2013-08-21 50 views
39

由于我们的应用程序有严格的性能和内存限制,我们的编码标准禁止使用默认堆—,即没有malloc,没有默认new。每个内存分配必须选择几个特定的​​分配器之一;像是否可以完全禁用默认的C++ new运算符?

// declared globally 
void* operator new(size_t size, CustomAllocHeap* heap, const char* perpetrator_name) 
{ 
    return heap->Allocate(size, perpetrator_name); 
} 
// imagine a bunch of CustomAllocHeap's declared globally or statically, thus 

Vector* v = new(gPhysicsHeap, __FUNCTION__) Vector(1.0f, 2.0f, 3.0f, 4.0f); 
// or in a class 
Thingy* p = new(this->LocalArenaHeap, __FUNCTION__) Thingy(); 

虽然我们保持良好的纪律性在这个与我们的代码,一些标准的C++组件(容器,std::function)暗中对默认new堆,这是非常糟糕的电话。

以某种方式完全禁用默认的new会很好,因此任何隐含导致默认分配的代码行都会立即引发编译器错误。这会让我们马上注意到这些事情。

我们可以明显地使new引发运行时错误

void* operator new (size_t) { __debugbreak(); return NULL; } 

,但它会更好,在编译时获得有关此警告。那可能吗?

我们的应用程序是为固定平台(带有Visual Studio的x64)构建的;便携性无关紧要。

+2

您是否尝试过寻找连接器输出?我认为你可以从链接器映射输出中知道allocator例程是否被调用。它并没有指出违规的路线,但至少它可以在建造时破门而入。 –

+0

这里有一个有趣的指针:http://bytes.com/topic/c/answers/854450-there-any-way-disable-global-operator-new – SirDarius

+2

是否可以实现默认(所有形式)的新/删除您的自定义新的?我似乎记得可以使用.def文件导出新/删除,导致使用导出版本的(整个?)过程。 – stijn

回答

17

您可以实现默认的new来调用一个未实现的函数。然后,在链接时,你会得到一个错误的裸new呼叫的用户:

#include <stdexcept> 
inline void * operator new (std::size_t) throw(std::bad_alloc) { 
    extern void *bare_new_erroneously_called(); 
    return bare_new_erroneously_called(); 
} 

当我测试IDEONE,我得到这个错误:

/home/geXgjE/ccrEKfzG.o: In function `main': 
prog.cpp:(.text.startup+0xa): undefined reference to `bare_new_erroneously_called()' 
collect2: error: ld returned 1 exit status 

在我的测试中,使用g++,如果在程序中没有对裸露的new的引用,则不存在链接错误。这是因为g++不会为未使用的inline函数发出代码。

我的系统上没有安装Visual Studio,因此以下信息仅基于我找到的一些文档。为了使嵌入的new运算符无处不在,您应该将其定义放在头文件中,然后在编译器中使用/FI detect_bare_new.h选项。 *根据this answer,Visual Studio将不会生成未使用的inline函数(如g++)的代码。但是,您应该检查是否存在需要为该行为启用的优化级别。

* g++ has a similar compiler option: -include detect_bare_new.h .

这假定您打算将自己的分配器传递给标准C++库中的C++模板和类。如果你不这样做,那么在调用默认分配器(将调用new)的标准头文件中插入代码也会触发链接错误。如果您希望允许标准C++库使用默认的new,那么使其工作的一个简单方法是以更长的编译时间为代价,将所有您打算包含的标准C++头添加到detect_bare_new.h的顶部文件。

您声明解决方案的可移植性对您并不重要。但为了完整起见,我应该强调Ben Voigt正确指出的问题:C++标准不保证不为未使用的函数生成代码的行为。因此,即使该函数未被使用,也可能会出现链接错误。但是,如果代码没有其他引用,除了在实现中存在残差new,该错误将在new定义本身内。例如,g++可以产生这样的错误:如果你的系统是一个未使用的inline功能生成代码

/home/QixX3R/cczri4AW.o: In function `operator new(unsigned int)': 
prog.cpp:(.text+0x1): undefined reference to `bare_new_erroneously_called()' 
collect2: error: ld returned 1 exit status 

,你仍然可以有一个解决办法。如果链接器将报告对未定义函数的所有错误引用,解决方法将工作。在这种情况下,如果观察到的唯一链接错误是由于运营商本身定义的new,则不会有意外呼叫到new。在验证代码只有单一错误之后,您可以更改链接线以包含具有bare_new_erroneously_called()的适当定义的对象或库,这会引发运行时异常。

+0

整洁的把戏,我需要记住:-) – cmaster

+0

你确定这是合法的根据标准? [这个问题的答案表明它可能不合法](http://stackoverflow.com/q/7297158/335858)。 – dasblinkenlight

+4

由于'operator new'不是一个模板,无论它是否被调用都会导致错误。 http://ideone.com/3KwAya –

1

如果你自己的“新”运营商未命名的“新”,但不同的(例如,“myNew”),你可以使用的方式“的#define”被垃圾代替“新”:

#define new *+-/& 

预编译器现在将取代“新”:

x = new mytype; 

通过垃圾:

x = *+-/& mytype; 

的优点相比,MES链接时的圣人是在编译C++文件时立即生成编译器消息,而不是在链接时结束。您还可以看到“新”所在的行。

缺点是您必须在项目中的所有C++文件中“#include”包含此“#define”的文件。

+2

我不知道在与预编译库链接时这是否会起作用 –

+0

@DanF:通常,编码标准不会扩展到以预编译形式提供的第三方库。我的建议的非内联版本(及其涵盖'malloc()'和朋友的扩展)可以检测到该库是否使用动态分配,但唯一的解决方案是覆盖'malloc()'和' new()'本身具有不同的行为,或重新编译违规库的修改版本。 – jxh

+1

但是这也会破坏我们*使用的新的(g_SpecificAllocator)Thingy()。 – Crashworks

0

Poison it!
如果使用GCC,对于这样的编译:

#ifdef __GNUC__ 

/* poision memory functions */ 
# pragma GCC poison malloc new 

#endif 
相关问题