是否可以完全禁用默认的C++ new运算符?

41

由于我们的应用程序有着很高的性能和内存限制,我们的编码标准禁止使用默认堆——也就是说,不能使用malloc或默认的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; }  

但最好在编译时就能收到有关此事的警告。这可行吗?

我们的应用程序是为一个固定平台(x64与Visual Studio)构建的;可移植性无关紧要。


2
你有尝试查看链接器输出吗?我认为你可以从链接器映射输出中看出分配器例程是否被调用。虽然它没有指向有问题的代码行,但至少可以在构建时进行断点调试。 - Michael Kohne
有一个有趣的指针在这里:http://bytes.com/topic/c/answers/854450-there-any-way-disable-global-operator-new,有没有办法禁用全局operator new? - SirDarius
2
在使用自定义 new 时,实现默认(所有形式的)new/delete 是否是一种选择?我记得可以通过 .def 文件导出 new/delete,从而使整个进程使用导出版本。 - stijn
@stijn 这正是 void* operator new ( size_t ) { __debugbreak(); return NULL; } 的作用:它替换了进程使用的默认 new 操作符,只需要链接即可。因此,OP 知道这个机制,并不认为它可以用来解决他的问题。 - cmaster - reinstate monica
对我来说,作者是否真的想完全禁用例如std::function的使用并不是很清楚,还是只想在它被使用时得到通知,然后提供修复方法。再次阅读问题,我猜测是第一种选择,但仍然不确定。 - stijn
显示剩余4条评论
4个回答

18

你可以实现默认的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选项。*根据这个答案,Visual Studio不会为未使用的inline函数(就像g++一样)生成代码。然而,您应该检查是否需要启用某个优化级别才能实现这种行为。

* g++有一个类似的编译器选项:-include detect_bare_new.h

这假定您打算向标准C++库中的C++模板和类传递自己的分配器。如果不这样做,调用默认分配器(它将调用new)的标准头文件中的内联代码也会触发链接错误。如果您希望允许标准C++库使用默认的new,那么一种简单的使其工作的方法(代价是更长的编译时间)是在detect_bare_new.h文件的顶部添加所有您打算包含的标准C++头文件。
您说解决方案的可移植性对您不重要。但出于完整性的考虑,我应该强调Ben Voigt正确指出的问题:C++标准不保证不为未使用的inline函数生成代码的行为。因此,即使未使用该函数,也可能出现链接错误。但是,如果代码除了在存根new实现中外没有对未实现函数的其他引用,则错误将在new定义本身中。例如,g++可能会生成以下错误消息:
/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

如果你的系统会为未使用的inline函数生成代码,你仍然可以有一个解决办法。这个解决办法只有在链接器能够报告所有对未定义函数的错误引用时才会生效。在这种情况下,如果唯一的链接错误是由于new操作符本身的定义而引起的,那么就不会有意外调用裸new的情况发生。在验证了代码只有单一错误后,你可以将链接行更改为包含一个具有适当定义的bare_new_erroneously_called()的对象或库,该函数将抛出运行时异常。


4
由于operator new不是一个模板,无论是否调用它,这都会导致错误。http://ideone.com/3KwAya - Ben Voigt
@BenVoigt 如果您将运算符 new 声明为内联的,会有任何变化吗? - Sergey Kalinichenko
2
@jxh:在某个特定的工具链和编译器以及链接器选项上可能是这样,但这并不是标准行为。 - Ben Voigt
1
@jxh:语法没有问题,但是该函数被odr-used并且您没有提供定义。 - Ben Voigt
@BenVoigt:是的,那会产生链接错误。我考虑的是即使它已经链接了(因为“weak”属性已经附加到它上面)。 - jxh
显示剩余7条评论

4

毒它吧!
如果您正在使用GCC,可以使用特定编译指令来实现此功能:

#ifdef __GNUC__

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

#endif

1
如果你自己的“new”操作符不是命名为“new”,而是其他名称(例如“myNew”),你可以使用“#define”以一种方式将“new”替换为垃圾:
#define new *+-/&

预编译器现在将替换“new”:

x = new mytype;

根据文本:“By the rubbish:”
x = *+-/& mytype;

与链接时的消息相比,它的优点在于编译C++文件时立即生成编译器消息,而不是在链接结束时。您还可以看到“new”所在的行。
缺点是您必须在项目中的所有C++文件中“#include”包含此“#define”的文件。

2
我不知道当与预编译的库链接时是否会起作用。 - Dan F
@DanF:通常,编码规范不会延伸到以预编译形式提供的第三方库。我的建议有一个非内联版本(并扩展其覆盖malloc()和其他类似函数),可以检测库是否使用动态分配内存,但唯一的解决方法是覆盖malloc()和new()本身的行为或重新编译修改后的受影响的库版本。 - jxh
1
但是这也会破坏new(g_SpecificAllocator) Thingy(),而我们确实使用它。 - Crashworks

1

您可以像禁用某些构造函数一样,声明= delete的new操作符。例如:

struct Foo {
    void* operator new(std::size_t) = delete;
    void* operator new[](std::size_t) = delete;
};

当你试图使用new关键字时,这将会导致编译器错误。请参见https://godbolt.org/z/RToOcf


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接