_CrtDumpMemoryLeaks() 不需手动调用,启用 _CRTDBG_LEAK_CHECK_DF 后 CRT 会在 exit 前自动触发检测;需在 main 首行设 _CrtSetDbgFlag,Debug 模式链接 MTd/MDd,并定义 _CRTDBG_MAP_ALLOC 和 new 宏以获取文件行号。

用 _CrtDumpMemoryLeaks() 在程序退出时自动检测泄漏
Windows 下 Visual Studio 自带的 CRT 调试堆能捕获未释放的堆内存,前提是启用调试堆并正确配置。它不依赖外部工具,适合快速验证单次运行是否存在泄漏。
- 必须在程序最开头(
main()第一行)调用_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); - 确保编译为 Debug 模式,且链接的是多线程调试版 CRT(如
MTd或MDd) - 不要手动调用
_CrtDumpMemoryLeaks()—— 设置了_CRTDBG_LEAK_CHECK_DF后,CRT 会在exit()前自动触发,此时调用栈信息才完整 - 泄漏报告中显示的文件名和行号,依赖于在包含
stdlib.h前定义_CRTDBG_MAP_ALLOC,并使用#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
用 AddressSanitizer(ASan)跨平台抓运行时泄漏
Clang 和 GCC 都支持 AddressSanitizer,它不仅能报内存泄漏,还能发现越界读写、UAF 等问题,且无需修改代码,只需编译选项开启。
- 编译命令示例:
g++ -g -O1 -fsanitize=address -fno-omit-frame-pointer leak.cpp -o leak
- 运行后若存在泄漏,会输出类似:
================================================================= ==12345==ERROR: LeakSanitizer: detected memory leaks Direct leak of 8 byte(s) in 1 object(s) allocated from: #0 0x7f... in operator new(unsigned long) (libclang_rt.asan.so) #1 0x56... in main leak.cpp:5 - 注意:默认只检测“direct leak”,即无任何指针指向的内存;若想查“indirect leak”(例如对象 A 持有 B,B 泄漏但 A 还活着),需额外加环境变量:
LSAN_OPTIONS=detect_leaks=2 - ASan 会显著拖慢运行速度、增大内存占用,**不适合压测或高频循环场景**,仅用于开发期排查
为什么 valgrind --leak-check=full 在 macOS 上跑不了
valgrind 官方长期不支持 macOS(尤其是 10.12+),主因是系统内核机制(KASLR、AMFI、SIP)与 valgrind 的二进制插桩原理冲突。你看到的 Unsupported architecture 或直接崩溃不是配置问题,而是根本不可用。
- macOS 替代方案只有两个可靠选择:
AddressSanitizer(推荐)或 Xcode 自带的Leaks工具(需 Instruments 图形界面 +malloc stack logging开启) - 如果坚持用 valgrind,唯一可行路径是用 Linux 虚拟机或 Docker 容器复现问题,**不能指望在 macOS 终端里编译出可用结果**
- 注意:Instruments 的 Leaks 检测逻辑不同——它基于 malloc 日志回溯引用链,对 C++ 对象生命周期推断较弱,容易漏掉 RAII 外的裸指针泄漏
自定义 new/delete 运算符做轻量级泄漏统计
当项目不允许引入 ASan、又需在 Release 模式下长期监控时,可全局重载分配器,记录地址、大小、调用位置,退出时遍历未匹配的 delete。
立即学习“C++免费学习笔记(深入)”;
- 核心是重载全局
::operator new和::operator delete,用std::unordered_map存活块(CallSite含__FILE__、__LINE__、size) - 必须处理
nothrow版本、数组版本(operator new[])、对齐版本(C++17operator new(size_t, std::align_val_t)),否则部分分配会绕过统计 - 多线程下需加锁(如
std::mutex),但锁本身可能引发死锁(比如在构造函数里 new 导致递归加锁),更安全的做法是用std::atomic_flag自旋或 TLS 存储待注册项 - 该方法无法捕获
malloc/free、VirtualAlloc等非 new 分配,也不能反映对象语义泄漏(比如智能指针循环引用)
detect_leaks=2”,或者在 macOS 上花半天配 valgrind 却不知道它早就弃疗了。真正有效的起点永远是:先确认泄漏是否真实存在(用 ASan 或 CRT),再决定要不要深入到自定义分配器级别。










