重载 new 和 delete 是排查内存泄漏最直接的手段,因其可在每次分配/释放时插入日志、堆栈捕获或计数逻辑,无需修改业务代码且不依赖外部工具;但须同时覆盖全局及数组版本,并妥善处理 size 获取、线程安全、递归调用与 STL 绕过等问题。

为什么重载 new 和 delete 是排查内存泄漏最直接的手段
因为 C++ 标准分配器不记录谁在什么位置申请了多大块内存,而重载全局 new/delete 能在每次分配/释放时插入日志、堆栈捕获或计数逻辑——不需要修改业务代码,也不依赖外部工具(如 Valgrind 或 ASan),适合嵌入式、游戏引擎或无法用调试器的环境。
但要注意:仅重载全局版本(::operator new / ::operator delete)不够,必须同时覆盖数组版本(operator new[] / operator delete[]),否则 new int[10] 类操作会绕过你的监控。
如何安全地重载全局 new 和 delete 并记录调用点
核心是用 __FILE__、__LINE__ 和 backtrace()(Linux)或 CaptureStackBackTrace(Windows)获取上下文。为避免递归调用(比如日志本身 malloc),所有记录逻辑必须使用栈内存或预分配缓冲区。
- 重载函数必须声明在全局作用域,且不能在头文件中重复定义(加
#pragma once或inline修饰符易出错,建议单独实现于一个.cpp文件) - 不要在重载函数里调用
std::cout、malloc、new或任何可能间接触发分配的函数 - 用静态数组缓存调用栈地址,再用
dladdr(Linux)或SymFromAddr(Windows)做符号解析;若不想依赖符号表,至少保留__FILE__和__LINE__ - 用原子计数器(
std::atomic_size_t)统计当前未释放字节数,避免多线程竞争
void* operator new(size_t size) {
void* ptr = malloc(size);
if (ptr) {
static std::atomic_size_t total_allocated{0};
total_allocated += size;
fprintf(stderr, "[ALLOC] %p %zu bytes at %s:%d\n", ptr, size, __FILE__, __LINE__);
}
return ptr;
}
void operator delete(void* ptr) noexcept {
if (ptr) {
static std::atomic_size_t total_allocated{0};
// 这里无法知道 size —— 实际需配合 malloc_usable_size 或自建映射表
fprintf(stderr, "[FREE] %p\n", ptr);
free(ptr);
}
}
如何补全 size 信息并支持匹配检查
标准 operator delete 不带 size 参数,所以单纯打印 __FILE__/__LINE__ 无法判断哪次 new 没被配对释放。解决方法是维护一张哈希表:以指针为 key,存 size + 分配位置 + 时间戳。
立即学习“C++免费学习笔记(深入)”;
- 哈希表本身必须用 mmap 分配(避免用
new初始化自己),或用固定大小环形缓冲区 + 线性查找(牺牲精度换安全性) - Linux 下可用
malloc_usable_size(ptr)近似还原 size(仅对 malloc 系分配有效,不适用于自定义对齐或operator new(std::align_val_t)) - 启用 C++17 的
operator new(size_t, std::align_val_t)重载时,必须同步实现对应delete版本,否则对齐分配会 fallback 到默认new,漏监控 - 程序退出前遍历未释放项,按 size 排序输出 top N 泄漏点,比单纯计数更易定位问题模块
实际使用时最容易忽略的三个坑
很多实现跑起来没报错,但根本抓不到泄漏——不是逻辑错,而是被编译器或运行时绕过了。
-
std::vector、std::string等 STL 容器默认使用std::allocator,它内部可能直接调用malloc而非operator new;要彻底监控,得传入自定义 allocator 或用链接期替换(如 LD_PRELOAD) - 静态对象构造期间的
new可能发生在你的重载函数初始化之前,导致首几笔分配丢失;把记录结构体定义为static全局变量而非局部 static,可确保优先构造 - Release 模式下编译器可能内联或优化掉
__FILE__/__LINE__,或把backtrace调用整个删掉;务必在 Release 构建中保留 debug info(-g)并禁用相关优化(如-fno-omit-frame-pointer)
真正有效的内存泄漏定位,从来不是靠“有没有重载”,而是看是否覆盖了所有分配路径、能否还原真实调用上下文、以及是否能在目标环境下稳定复现——其余都是细节。











