C++ – 在项目中替换全局 new/delete 函数

函数签名

参考文档:newdelete

即使不包含 标准库 头文件,版本 可替换分配函数 与 可替换解分配函数 也会在每个翻译单元隐式声明。版本 可替换分配函数 与 可替换解分配函数 可以替换:在程序任意位置定义并在任意源文件的用户提供的拥有相同签名的非成员函数都会替换默认版本。它的声明不需要可见。

这意味着我们在替换全局 new/delete 函数时,无需在 .h 中声明,只需要在 .cpp 中定义他们。

对于某个可以替换的函数,如果程序中提供了它的多个替换,或它有带 inline 说明符的替换声明,那么程序非良构,不要求诊断。如果替换在全局命名空间以外的命名空间中定义,或它被定义成在全局作用域的静态非成员函数,那么程序非良构。

对于同一个模块而言,替换的全局 new/delete 函数只能有一个实现,且说明符要与标准库要求相同。

nothrow 版本的标准库实现直接调用对应的抛出版本。抛出的数组版本的标准库实现直接调用对应的单对象版本。具大小解分配函数的标准库实现直接调用对应的不具大小解分配函数。因此只需要替换抛出单对象分配函数就可以处理所有分配。(C++11 起)

这代表我们只需要替换 4 个版本的函数即可实现全部函数,如下。

void* operator new(std::size_t count);
void* operator new(std::size_t count, std::align_val_t al);
void operator delete(void* ptr) noexcept;
void operator delete(void* ptr, std::align_val_t al) noexcept;
  • 通用分配函数
  • 有内存对齐要求的分配函数
  • 通用解分配函数
  • 有内存对齐要求的解分配函数

如何替换

根据上面的分析,我们只需要在 .cpp 中替换上面的 4 个函数皆可,但是实际使用时就会发现,这种方案只在 .cpp 所在的模块有效,在跨越模块边界的时候,所替换的 new/delete 函数可能不会被导出,我们替换的函数能否跨越边界是特定于编译器的,所以需要一种跨平台的解决方案。

这里选择 Unreal 引擎中使用的方案,定义了一个宏,在宏中完成对 new/delete 函数的替换,然后要求所有的模块均在自己的 .cpp 中放置此宏。下面是一个可能的实现。

#define REPLACEMENT_OPERATOR_NEW_AND_DELETE                                                                                              \
    void* operator new(std::size_t Count)                             { return FMemory::Malloc(Count);                                 } \
    void* operator new(std::size_t Count, std::align_val_t Alignment) { return FMemory::Malloc(Count, static_cast<size_t>(Alignment)); } \
    void operator delete(void* Ptr)                             noexcept { FMemory::Free(Ptr); }                                         \
    void operator delete(void* Ptr, std::align_val_t Alignment) noexcept { FMemory::Free(Ptr); }

发表回复