重载 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 + 分配位置 + 时间戳。
new 初始化自己),或用固定大小环形缓冲区 + 线性查找(牺牲精度换安全性)malloc_usable_size(ptr) 近似还原 size(仅对 malloc 系分配有效,不适用于自定义对齐或 operator new(std::align_val_t))operator new(size_t, std::align_val_t) 重载时,必须同步实现对应 delete 版本,否则对齐分配会 fallback 到默认 new,漏监控很多实现跑起来没报错,但根本抓不到泄漏——不是逻辑错,而是被编译器或运行时绕过了。
std::vector、std::string 等 STL 容器默认使用 std::allocator,它内部可能直接调用 malloc 而非 operator new;要彻底监控,得传入自定义 allocator 或用链接期替换(如 LD_PRELOAD)new 可能发生在你的重载函数初始化之前,导致首几笔分配丢失;把记录结构体定义为 static 全局变量而非局部 static,可确保优先构造__FILE__/__LINE__,或把 backtrace 调用整个删掉;务必在 Release 构建中保留 debug info(-g)并禁用相关优化(如 -fno-omit-frame-pointer)真正有效的内存泄漏定位,从来不是靠“有没有重载”,而是看是否覆盖了所有分配路径、能否还原真实调用上下文、以及是否能在目标环境下稳定复现——其余都是细节。