Google Test单元测试中重载operator delete未全局生效的问题
解决Google Test与自定义new/delete重载的内存检测冲突问题
我之前踩过一模一样的坑!核心原因是Google Test(GTest)的内存管理逻辑和我们自定义的全局new/delete重载存在「时序错位」和「路径绕过」的问题,才会出现部分分配走重载、部分不走,进而导致误报和malloc异常的情况。
先搞懂为什么会出问题
- GTest早于你的重载初始化:GTest的全局测试对象、内部日志系统等模块,会在你的全局new/delete重载生效前就完成内存分配(比如在main函数启动前的全局构造阶段)。这些内存的释放不会走你的delete重载,自然会被你的跟踪代码误判为泄漏。
- GTest直接调用底层分配器:为了避免依赖用户自定义的分配逻辑,GTest的某些核心模块会直接调用
malloc/free、aligned_alloc这类底层函数,完全绕开全局new/delete。你的跟踪代码看不到这些分配操作,一旦GTest释放这些内存,就会出现「释放未记录的内存」误报;反过来如果GTest没释放,又会漏报泄漏。 - 重载不完整触发异常:如果你的最简版只重载了普通的
operator new和operator delete,没覆盖带std::nothrow、数组、对齐的版本,GTest调用这些未重载的版本时,可能因为分配逻辑不一致触发异常,就是你碰到的malloc返回异常问题。
具体解决方案
方案1:让GTest跳过自身内存的泄漏检测
GTest提供了官方flag来禁用自身的泄漏检查,避免误报。你可以在测试启动的main函数里添加:
int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); // 禁用GTest自身的退出时泄漏检查 testing::GTEST_FLAG(leak_check_at_exit) = false; return RUN_ALL_TESTS(); }
如果需要更精细的控制,也可以在自定义测试环境中动态开关,但这个flag是最直接的。
方案2:重载所有版本的全局new/delete
一定要覆盖C++标准规定的所有全局new/delete重载版本,确保GTest的任何内存分配都会走你的跟踪逻辑。完整实现示例:
#include <new> #include <cstdlib> #include <iostream> // 普通抛出异常的new void* operator new(std::size_t size) { void* ptr = std::malloc(size); std::cout << "Allocated: " << ptr << " (" << size << " bytes)\n"; if (!ptr) throw std::bad_alloc(); return ptr; } // 普通delete void operator delete(void* ptr) noexcept { if (ptr) { std::cout << "Freed: " << ptr << "\n"; std::free(ptr); } } // 不抛出异常的new(nothrow版本) void* operator new(std::size_t size, const std::nothrow_t&) noexcept { void* ptr = std::malloc(size); std::cout << "Allocated (nothrow): " << ptr << " (" << size << " bytes)\n"; return ptr; } // nothrow版本的delete void operator delete(void* ptr, const std::nothrow_t&) noexcept { if (ptr) { std::cout << "Freed (nothrow): " << ptr << "\n"; std::free(ptr); } } // 数组版本的new void* operator new[](std::size_t size) { void* ptr = std::malloc(size); std::cout << "Allocated array: " << ptr << " (" << size << " bytes)\n"; if (!ptr) throw std::bad_alloc(); return ptr; } // 数组版本的delete void operator delete[](void* ptr) noexcept { if (ptr) { std::cout << "Freed array: " << ptr << "\n"; std::free(ptr); } } // C++17及以上的对齐版本new void* operator new(std::size_t size, std::align_val_t align) { void* ptr = std::aligned_alloc(static_cast<std::size_t>(align), size); std::cout << "Allocated aligned: " << ptr << " (" << size << " bytes, align: " << static_cast<std::size_t>(align) << ")\n"; if (!ptr) throw std::bad_alloc(); return ptr; } // C++17及以上的对齐版本delete void operator delete(void* ptr, std::align_val_t align) noexcept { if (ptr) { std::cout << "Freed aligned: " << ptr << "\n"; std::free(ptr); } }
注意:对齐版本的delete在C++17中必须单独重载,否则会触发未定义行为,这也是malloc异常的常见诱因。
方案3:直接用GTest内置的泄漏检测(最省心)
其实GTest本身已经内置了成熟的内存泄漏检测功能,完全可以替代你自定义的new/delete重载:
- 大部分平台编译GTest时默认开启了泄漏检测(定义
GTEST_HAS_LEAK_DETECTION=1),测试结束后会自动报告泄漏。 - 如果需要临时禁用某段代码的泄漏检测,可以用
testing::LeakDisabler:
TEST(MyTest, AllowLeak) { testing::LeakDisabler disabler; // 这段代码的内存泄漏不会被GTest报告 int* intentional_leak = new int; }
方案4:隔离被测代码与GTest的内存分配
如果必须用自己的跟踪工具,可以把被测代码和你的new/delete重载编译成独立的库,让GTest的代码不链接这个库:
- 将被测代码+自定义new/delete重载编译为静态库
lib被测代码.a。 - 测试用例代码链接
lib被测代码.a和GTest库。 - GTest的main函数单独编译,不链接
lib被测代码.a。
这样只有被测代码的内存分配会走你的重载,GTest自身的分配用默认逻辑,彻底避免冲突。
总结
最推荐方案2+方案3:先补全所有new/delete重载避免异常,然后用GTest内置的泄漏检测替代自定义跟踪,既稳定又省心。如果必须保留自己的跟踪工具,就用方案1+方案2或者方案4。
内容的提问来源于stack exchange,提问作者Dtor
相关产品推荐
相关产品推荐

