windows内存泄露定位方法

windows内存泄露定位方法
内存泄露(Memory Leak)是C/C++程序经常遇到的一个棘手问题。简单来说,内存泄露就是没有释放本来应该释放的内存。
可以把解决内存泄露问题分成两步,第一步是定位到泄露的堆块,第二步是定位到泄露堆块是哪段代码分配的。本文介绍如何使用CRT堆的调试支持来实现这两个目标。

1._CrtDumpMemoryLeaks
CRT设计了一个名为_CrtDumpMemoryLeaks的函数来检测和报告发生在堆上的内存泄露。

调用后产生类似如下的信息:
线程 0x1c98 已退出,返回值为 0 (0x0)。
Detected memory leaks!
Dumping objects ->
D:\cpp\vs2019\memleak\memleak\memleak.cpp(29) : {87} normal block at 0x00890838, 100 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
D:\cpp\vs2019\memleak\memleak\memleak.cpp(26) : {86} normal block at 0x008907F8, 20 bytes long.
 Data: <beijing2013     > 62 65 69 6A 69 6E 67 32 30 31 33 00 CD CD CD CD 
Object dump complete.
程序“[14228] memleak.exe”已退出,返回值为 0 (0x0)。


2.何时调用
上面的函数,无论何时调用,都会将当前堆中未是放过的块认为是泄露而转储出来。为了防止把将来能正常释放的块误当作内存泄露,我们必须选择合适的时机来调用这个函数。太早调用,会误判。
何时调用呢?CRT的退出函数(exit 和 doexit)会在执行完终结器(包含全局对象析构)后,再调用_CrtDumpMemoryLeaks
只要设置一个标记就会激活这个函数检查:
_crtDbgFlag |= _CRTDBG_LEAK_CHECK_DF;

3.定位导致泄露的源码
定义一个标记_CRTDBG_MAP_ALLOC
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>

注意顺序不能错,这个标记影响crtdbg.h的编译,从而影响malloc的函数定义。
这样处理后,malloc就可以打印行号了,但new还不行。

需要定义调试版本的new 
#ifdef _DEBUG
#define MYDEBUG_NEW   new( _NORMAL_BLOCK, __FILE__, __LINE__)
#define new MYDEBUG_NEW
#endif// _DEBUG

这样就可以打印完整了。

4.快速找到泄露
观察转储打印:
D:\cpp\vs2019\memleak\memleak\memleak.cpp(26) : {86} normal block at 0x008907F8
其中的{86} 代表第86次分配内存操作发生了泄露,你可能要说,我只new过一次,怎么会是第86次?这很容易理解,其他的内存申请操作在C的初始化过程调用的呗。:)
有没有可能,我们让程序运行到第52次内存分配操作的时候,自动停下来,进入调试状态?所幸,crtdbg确实提供了这样的函数:即 long _CrtSetBreakAlloc(long nAllocID)。我们加上它:
_crtBreakAlloc = 86;
这只这个全局变量即可。

你发现,程序运行到 p1 = (char*)new char[20]; 一句时,自动停下来进入调试状态。细细体会一下,你可以发现,这种方式你获得的信息远比在程序退出时获得文件名及行号有价值得多。因为报告泄漏文件名及行号,你获得的只是静态的信息,然而_CrtSetBreakAlloc则是把整个现场恢复,你可以通过对函数调用栈分析(我发现很多人不习惯看函数调用栈,如果你属于这种情况,我强烈推荐你去补上这一课,因为它太重要了)以及其他在线调试技巧,来分析产生内存泄漏的原因。通常情况下,这种分析方法可以在5分钟内找到肇事者。

当然,_CrtSetBreakAlloc要求你的程序执行过程是可还原的(多次执行过程的内存分配顺序不会发生变化)。这个假设在多数情况下成立。不过,在多线程的情况下,这一点有时难以保证。

5. 程序源码:--来自软件调试这本书
#include <stdio.h>
#include <string.h>
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#include <malloc.h>

#ifdef _DEBUG
#define MYDEBUG_NEW   new( _NORMAL_BLOCK, __FILE__, __LINE__)
#define new MYDEBUG_NEW
#endif// _DEBUG


#pragma warning(disable : 4996)

int main()
{
    char* p1, * p2;
    printf("Sample to demo memory leak checking of RTC by Raymond!\n");

    _crtDbgFlag |= _CRTDBG_LEAK_CHECK_DF;
    _crtBreakAlloc = 86;

    p1 = (char*)new char[20];
    strcpy(p1, "beijing2013");

    p2 = (char*)malloc(100);

    return 0;

}
 

猜你喜欢

转载自blog.csdn.net/hb_zxl/article/details/115423792