
为什么重载 operator new 和 operator delete 是监控内存泄漏的最直接方式
因为所有用 new 和 delete 分配/释放的堆内存,都会经过这两个函数(除非显式调用 malloc/free 或禁用全局重载)。不侵入业务代码、不依赖编译器插桩、也不需要链接额外库——只要重载它们,就能在分配时记下地址+大小+调用栈,在释放时抹除记录。最终程序退出前未被抹除的条目,就是潜在泄漏点。
注意:必须同时重载带 std::size_t 参数的 operator new 和 operator delete,以及带 noexcept 的版本(C++11 起默认加),否则某些 STL 容器或异常路径下的分配可能绕过你的监控。
如何在重载中安全记录调用栈和分配信息
仅记录 __FILE__ 和 __LINE__ 远不够——同一行多次 new 无法区分;而完整调用栈能定位到真正申请位置。推荐用 backtrace()(Linux/glibc)或 CaptureStackBackTrace()(Windows)获取帧地址,再用 backtrace_symbols() 或 SymFromAddr() 解析符号。但要注意:
-
backtrace()在信号处理函数中不安全,不要在SIGSEGV处理里调用 - 解析符号需链接
-lbfd -ldl(Linux)或初始化dbghelp.dll(Windows),且调试信息必须存在(-g编译) - 频繁调用栈采集开销大,建议只在 DEBUG 模式启用,或加运行时开关控制
示例关键片段:
立即学习“C++免费学习笔记(深入)”;
void* operator new(std::size_t size) {
void* ptr = malloc(size);
if (ptr) {
record_allocation(ptr, size, __FILE__, __LINE__);
}
return ptr;
}为什么必须重载数组版本的 operator new[] 和 operator delete[]
因为 new T[N] 和 delete[] p 调用的是独立的运算符,不是普通 new/delete。漏掉它们会导致数组分配被记录、但释放时不匹配,误报为泄漏。更隐蔽的问题是:如果只重载了单对象版本,而用户写了 new int[10],编译器会调用默认的 operator new[](可能直接转给 malloc),你的监控完全失效。
务必成对实现:
void* operator new[](std::size_t size)void operator delete[](void* ptr) noexcept- (可选)带
std::align_val_t的 C++17 对齐版本,否则对aligned_new无感知
程序退出时如何可靠触发泄漏报告而不引发二次崩溃
不能依赖全局对象析构顺序(不确定)、也不能靠 atexit() 中做复杂操作(此时堆可能已部分损坏)。稳妥做法是:
- 在
main()返回前手动调用dump_leaks() - 或用静态局部变量 + 析构函数,但确保该变量定义在
main所在编译单元(避免跨 TU 初始化顺序问题) - 报告时禁止调用
std::cout(可能已被销毁),改用write(2, ...)或OutputDebugString() - 遍历未释放记录时,跳过已
free但未delete的指针(比如混用malloc/delete)——这类属于 UB,不归监控器管
真正容易被忽略的是:线程局部存储(TLS)中的分配。若监控器用 thread_local 记录,主线程退出后其他线程仍在运行,数据可能被重复报告或访问已释放内存。统一走全局哈希表 + 读写锁更稳妥。










