1、在mai函数中增加了_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF);
在debug下程序退出时,输出会打印很多内存泄露情况。 我们根据这个特性,原理可以实现更强大的代码级检测机制。
_CRTDBG_LEAK_CHECK_DF标记的含义比较简单,就是在程序退出时调用函数_CrtDumpMemoryLeaks,该函数从内存分配的队列里面获取,
还有哪些内存没有释放。
UMDH的实现原理:在某个时间点开启抓取一段当前内存分配情况,在另一个时间段停止抓取内存分配情况。然后对比,stop-start的就是泄露的内存了。
umdh是测试期,利用gflag来跟踪堆的分配情况,很多不是源代码级的,看起来比较费劲,如果能在开发阶段解决内存泄露成本是最低的
2、每个内存new出来后,在内存上方都会多一个结构体,来跟踪记录这段内存(可以尝试连续两次new,查看两个new的地址差),这个结构体就是_CrtMemBlockHeader(release下不会分配)
typedef struct _CrtMemBlockHeader
{
struct _CrtMemBlockHeader * pBlockHeaderNext; // 下一个new的位置
struct _CrtMemBlockHeader * pBlockHeaderPrev;// 上一个new的 位置
char * szFileName; // 调用new的文件名
int nLine;// 调用new的行号
#ifdef _WIN64
/* These items are reversed on Win64 to eliminate gaps in the struct
* and ensure that sizeof(struct)%16 == 0, so 16-byte alignment is
* maintained in the debug heap.
*/
int nBlockUse;
size_t nDataSize;
#else /* _WIN64 */
size_t nDataSize; // 分配的内存大小
int nBlockUse; // 块类型,用户态,内核态等,我们只需要关心_NORMAL_BLOCK的类型块就好
#endif /* _WIN64 */
long lRequest; // 块号,每块内存的id号
unsigned char gap[nNoMansLandSize];
/* followed by:
* unsigned char data[nDataSize];
* unsigned char anotherGap[nNoMansLandSize];
*/
} _CrtMemBlockHeader;
windows下会记录这个链表,首地址是个全局变量
static _CrtMemBlockHeader * _pFirstBlock;
它还有几个很好的函数可供使用
typedef struct _CrtMemState
{
struct _CrtMemBlockHeader * pBlockHeader;
size_t lCounts[_MAX_BLOCKS]; // new的数量
size_t lSizes[_MAX_BLOCKS];// new的大小,_MAX_BLOCKS是块类型,用户分配的块类型是_NORMAL_BLOCK
size_t lHighWaterCount;
size_t lTotalCount;
} _CrtMemState;
_CrtMemState current_mem_state;
_CrtMemCheckpoint(¤t_mem_state); // 保存当前的内存链表
3、有了2这些信息,再实现内存泄露检测就显得非常简单了
在认为可以开始检测的时候调用一次_CrtMemCheckpoint(&start_mem_state);
在运行一段时间后调用一次_CrtMemCheckpoint(&stop_mem_state);
比较两个链表,如果stop有,但是start没有,就是新分配的_CrtMemBlockHeader了,打印出文件信息,行号就可以了。
4、上面看似已经没有问题了,但是,但是效果真的可以吗?
实际操作后会发现_CrtMemBlockHeader中的两个值是空!!!
char * szFileName; // 调用new的文件名
int nLine;// 调用new的行号
这也是为什么步骤1描述的程序退出时打印了很多内存泄露,但是只有一个块号,大小,这个我们是查不到原因的。
Detected memory leaks!
Dumping objects ->
{145} normal block at 0x006FD878, 95832 bytes long. // 145就是块号,normal block 是类型
Data: < @ > CD CD CD CD 01 00 00 00 00 00 00 00 00 00 40 00
Object dump complete.
回过来想想也对,new的时, _CrtMemBlockHeader怎么会知道你的文件信息,行号了。没有关系这个问题也有解决办法
#ifdef _DEBUG
#define DBG_NEW_UTIL_TEST new (_NORMAL_BLOCK, __FILE__, __LINE__)
#else
#define DBG_NEW_UTIL_TEST new // release下继续使用默认的
#endif
windows提供了另外一种new的方式,参数增加了行号,列号。该new的原型在dbgnew.cpp文件中
void *__CRTDECL operator new(
size_t cb,
int nBlockUse,
const char * szFileName,
int nLine
)
在我们的代码中后续将new修改为DBG_NEW_UTIL_TEST即可了
5、结束语:原理已经讲完,源码就不提供了,可以根据上面原理,自己实现。
另外还有几个与此相关的几个函数,网上可以查到它们的帮助文档
_CrtMemDifference
_CrtMemDumpAllObjectsSince
_CrtSetBreakAlloc
_CrtSetAllocHook
_CrtSetDbgFlag
参考书目<<visual c++.net 运行库函数大全>>,msdn帮助