Finding Memory Leaks Using the CRT Library
CRT 有一个非常好用的, 检测 mem leak 的子系统, 下面介绍一下它的用法. msdn 官网 doc: http://msdn.microsoft.com/en-us/library/x98tx3cf.aspx (msdn 原文中有一些谬误, 我经过自己的实践发现和其描述不符, 请见下文 'msdn 勘误' 部分)
本文主要介绍了如下技术:
- 输出某时间点 mem leak 信息.
- 检测某段函数 mem leak 情况.
- 程序退出后的 mem leak 检测.
预备知识:
所谓 CRT 检测 mem leak 机制, 就是使用带追踪信息的函数( _malloc_dbg/_free_dbg 以及 new(_CLIENT_BLOCK, __FILE__, __LINE__)/delete )代替传统的函数.
先看代码中和注释吧:
// DebugMemLeak.h // Recommand include me in stdafx.h to enable memory check in debug moode. // For more details: http://msdn.microsoft.com/en-us/library/x98tx3cf.aspx #pragma once #define _CRTDBG_MAP_ALLOC // When the _CRTDBG_MAP_ALLOC flag is defined in the debug version of an application, // the base version of the heap functions are directly mapped to their debug versions. // The flag is used in Crtdbg.h to do the mapping. This flag is only available // when the _DEBUG flag has been defined in the application. // For the CRT functions to work correctly, the #include statements must follow the order shown here. #include <stdlib.h> #include <crtdbg.h> // Including crtdbg.h maps the malloc and the free functions to their debug versions, _malloc_dbg and free, // which track memory allocation and deallocation. This mapping occurs only in debug builds, which have _DEBUG. // Release builds use the ordinary malloc and free functions. #ifdef _DEBUG #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ ) // The #define statement maps a base version of the CRT heap functions // to the corresponding debug version. If you omit the #define statement, // the memory leak dump will be less detailed. #define new DBG_NEW #endif // _DEBUG
上述 DebugMemLeak 中的各个部分功能, 注释中已经很详尽了. 其中有几点注意事项:
1. 中间的两个 `include` 顺序不能颠倒.
2. 仅仅在有 _DEBUG 的编译模式下, 这个子系统才工作. 否则, 所有 malloc/free 以及 new/delete 都是不同的, 不带 debug 能力的函数. (在 release 模式下, 即使你手动调用 _malloc_dbg 也将映射到普通的 malloc)
基本用法介绍:
1. 输出某时间点 mem leak 信息.
#include "DebugMemLeak.h" int _tmain(int argc, _TCHAR* argv[]) { // Leak! auto pI = new int[1]; pI = nullptr; // Dump the memory leak. Result as follow: /************* Detected memory leaks! Dumping objects -> e:\work\memleakdemo\memleakdemo.cpp(27) : {120} client block at 0x003DB448, subtype 0, 4 bytes long. Data: < > CD CD CD CD Object dump complete. **************/ _CrtDumpMemoryLeaks(); // After you have enabled the debug heap functions by using these statements, // you can place a call to _CrtDumpMemoryLeaks before an application exit point // to display a memory-leak report when your application exits. // By default, _CrtDumpMemoryLeaks outputs the memory-leak report to the Debug pane of // the Output window. You can use _CrtSetReportMode to redirect the report to another // location: `_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );` return 0; }
`_CrtDumpMemoryLeaks()` 函数打印当前状态下 heap 的信息:
怎么样, 超级赞吧.
2. 检测某段函数 mem leak 情况.
#include "DebugMemLeak.h" void foo() { new int[1]; } int _tmain(int argc, _TCHAR* argv []) { _CrtMemState cmsBefore = {}, cmsAfter = {}; _CrtMemCheckpoint(&cmsBefore); // Snapshot memory usage before you function. // Do what you want. // And something else.... foo(); _CrtMemCheckpoint(&cmsAfter); // Snapshot memory usage after you function. _CrtMemState cmsDiff = {}; if (_CrtMemDifference(&cmsDiff, &cmsBefore, &cmsAfter)) // Compare the difference. { _CrtMemDumpStatistics(&cmsDiff); // Dump the memory leak. } return 0; }
`_CrtMemCheckpoint` 非常适合检测某段调用的 mem leak 问题.
3. 程序退出后的 mem leak 检测.
int _tmain(int argc, _TCHAR* argv []) { // Set memory allocation in debug mode and // dump the memory leak information after program exit. _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF // Turn on debug allocation. //| _CRTDBG_DELAY_FREE_MEM_DF // Don't actually free memory. //| _CRTDBG_CHECK_ALWAYS_DF // Check heap every alloc/dealloc. //| _CRTDBG_RESERVED_DF // Reserved - do not use. //| _CRTDBG_CHECK_CRT_DF // Leak check/diff CRT blocks. | _CRTDBG_LEAK_CHECK_DF // Leak check at program exit. ); new int[1]; return 0; }
经验介绍:
1. 首先使用 `_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );` 在程序退出时检测整个程序是否有内存泄露.
2. 如果上面的检测有泄漏, 并且无法直观的肉眼找到, 则通过:
_CrtMemState s1, s2, s3; _CrtMemCheckpoint( &s1 ); foo(); // 这是被检测的函数. _CrtMemCheckpoint( &s2 ); if ( _CrtMemDifference( &s3, &s1, &s2) ) { _CrtMemDumpStatistics( &s3 ); }
这种方法可以快速定位到泄露点.
3. 最后, 根据自己情况, 配合使用 `_CrtDumpMemoryLeaks` 随时输出 mem 信息, 帮助分析即可.
msdn 勘误:
图中红圈部分, #define 说的是箭头所标记的地方. 但实际经过测试,
而实际上, 如果想输出有意义的, 带 __FILE__ 以及 __LINE__ 的 dump 信息, 需要使用 `new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )`!