可重现内存泄漏调试
1. 调试原理
VS内存泄漏调试基于CRT库的支持,其原理是调用内存分配的另外一个实现实例,从而记录内存分配时的程序信息(包括文件名和行号等)。在程序运行结束时,调用CRT的内存检测函数,定位到第几次分配的内存没有释放。之后设置内存分配函数在相应的次数之后中断,观察函数调用堆栈,从而确定用户代码分配内存的地方。
2. 情景模拟
(1). 在Debug版本时,调用new的另外一个实现实例,如下所示:
#ifdef _DEBUG |
这个实现实例是CRT的内存调试功能,其函数原型及定义如下所示:
void *__CRTDECL operator new( |
(2). 模拟内存泄漏
char* p = new char[10]; |
(3). 在main函数退出之前调用CRT的内存分析函数
_CrtDumpMemoryLeaks(); |
(4). 程序运行完毕后,“输出(Output)窗口”会显示内存分析报告,如下所示
其中花括号中的数字{N}代表的是此块内存是系统第N次分配的,如果我们想知道是在程序源文件具体的哪一行分配的,只需要在系统第N次分配时下断点,然后根据函数调用堆栈就可以定位到用户代码调用new分配内存的那一行。
3. 泄漏分析
(1). 我们获得第N次分配的内存没有被释放后,可以通过如下函数,让程序在第N次分配时暂停下来。
_CrtSetBreakAlloc(N); |
(2). 针对本问的例子,我们可以设置N为119,程序在分配到第119次内粗时会触发一个断点。
此时break程序执行,观察函数堆栈,如下所示:
根据函数堆栈,就可以定位到用户代码调用内存分配的地方了。
4. 优缺点
优点:
ü 易用性:借助与VS辅助平台,分析方便
缺点:
入侵性:需要在程序代码中添加CRT的辅助函数
局限性:只能分析泄漏内存的分配序号是固定的内存泄漏,并且需要源代码和Debug程序版本
Windbg+CRT内存泄漏调试
1. 调试原理
在调试版本,用CRT辅助函数,找到泄漏内存块的地址,之后通过结合Windbg的!heap命令对进程堆的内存进行跟踪和分析,从而找到内存泄漏的地方。
2. 情景模拟
模拟内存泄漏的代码如下所示:
// MemleakTest.cpp : Defines the entry point for the console application. //
#include "stdafx.h"
#ifdef _DEBUG #define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __FILE__, __LINE__) #else #define DEBUG_CLIENTBLOCK #endif #define _CRTDBG_MAP_ALLOC #include <crtdbg.h> #ifdef _DEBUG #define new DEBUG_CLIENTBLOCK #endif #include <process.h>
int _tmain(int argc, _TCHAR* argv[]) { _CrtSetBreakAlloc(119);
char* p = new char[10];
_CrtDumpMemoryLeaks(); system("pause"); return 0; }
|
3. 泄漏分析
(1). 用VS以调试的方式运行上述代码,执行到system(“pause”)时,观察输出窗口的内容,如下所示:
其中泄漏的内存块的地址为0x03196FF0
(2). 用VS剥离当前调试进程,并用Windbg附加到剥离的进程,设置好程序的符号路径后,用!heap命令进行此块内存的调用堆栈分析。
根据打印出来的函数堆栈,我们可以定位到分配此块内存时用户程序函数中的偏移量。
(3). 用ln命令查找此偏移量所在的源文件及行号
查看源代码,发现此处正好是我们分配此块内存的地方。
4. 优缺点
优点:
ü 易用性:借助CRT打印内存泄漏地址,用Windbg直接可以分析出泄漏内存的分配地址。
缺点:
入侵性:需要在程序代码中添加CRT的辅助函数
局限性:需要源代码和Debug程序版本