You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的代码不链接这个库:

  1. 将被测代码+自定义new/delete重载编译为静态库lib被测代码.a。
  2. 测试用例代码链接lib被测代码.a和GTest库。
  3. GTest的main函数单独编译,不链接lib被测代码.a。

这样只有被测代码的内存分配会走你的重载,GTest自身的分配用默认逻辑,彻底避免冲突。

总结

最推荐方案2+方案3:先补全所有new/delete重载避免异常,然后用GTest内置的泄漏检测替代自定义跟踪,既稳定又省心。如果必须保留自己的跟踪工具,就用方案1+方案2或者方案4。

内容的提问来源于stack exchange,提问作者Dtor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:59:28