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

时间:2013-08-21 19:16:51

标签: c++ new-operator

由于我们的应用程序具有严格的性能和内存限制,我们的编码标准禁止使用默认堆 - 即,没有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; }  

但是在编译时得到关于此的警告要好得多。这可能吗?

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

4 个答案:

答案 0 :(得分: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选项。 * 根据到this answer,Visual Studio不会为未使用的inline函数生成代码(如g++)。但是,您应该检查是否存在需要为该行为启用的优化级别。

  

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

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

您声明解决方案的可移植性对您来说并不重要。但为了完整起见,我应该强调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()的对象或库,这将导致运行时异常。

答案 1 :(得分:1)

如果您自己的“新”运算符未命名为“new”但名称不同(例如“myNew”),则可以使用“#define”替换“new”垃圾:

#define new *+-/&

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

x = new mytype;

垃圾:

x = *+-/& mytype;

与链接时消息相比的优势在于,这将在编译C ++文件时立即生成编译器消息,而不是在链接时最终生成。您还可以看到“新”所在的行。

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

答案 2 :(得分:0)

Poison it!
如果您正在使用GCC,那么就有一个实用工具:

#ifdef __GNUC__

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

#endif

答案 3 :(得分:0)

您只需声明运算符new = delete,类似于禁用某些构造函数的方法。喜欢

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

当您尝试对此类使用new时,这会给您编译器错误。参见https://godbolt.org/z/RToOcf

相关问题