VS环境中进行内存泄漏的检测

根据MSDN中的介绍,亲测整理。

本篇比较长,如不愿花费太多时间,可只看第一段和第四段,甚至只看第四段。

内存泄漏,即未能正确释放以前分配的内存,是 C/C++ 应用程序中最难以捉摸也最难以检测到的 Bug 之一。借助 Visual Studio 调试器和 C 运行时 (CRT) 库,可以检测和识别内存泄漏。检测内存泄漏的主要工具是调试器和 C 运行库 (CRT) 调试堆函数。

简单的使用

要调用CRT调试堆函数,需包含头文件<crtdbg.h>。

在程序的退出点之前调用函数

_CrtDumpMemoryLeaks

();可简单的显示内存泄漏报告。
示例:
#include "stdlib.h"
#include <crtdbg.h>
int main()
{
    char *p =new char[30];
    char *p1=(char *)malloc(sizeof(char)*10);
    _CrtDumpMemoryLeaks();
    return 0;
}

输出结果:

clip_image002

当程序有多个退出点时,在每一个退出点都调用_CrtDumpMemoryLeaks()显然不是一个好主意,在程序的开头部分调用函数_CrtSetDbgFlag会导致在每个退出点自动调用 _CrtDumpMemoryLeaks。如此调用_CrtSetDbgFlag必须设置两个位域

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

解释内存泄漏报告

{64}—— 表示是在第64次分配的内存,最终没有被释放掉,从而导致泄漏。CRT 报告包含运行 过程中的所有内存块分配情况。其中包括 CRT 库和其他库(如 MFC)的分配情况。 所以不要疑惑这里为什么不是2.

Normal block—— 表示被泄露的内存块为由程序分配的普通内存。另外,这个值还有可能是

Client Blocks ,“客户端块”是由 MFC 程序用于需要析构函数的对象的特殊 类型内存块。 MFC new 运算符根据正在创建的对象的需要创建普通块或客 户端块。

0x004C1DA0—— 表示发生泄漏的内存位置

10 bytes long.—— 遭泄露的内存块的大小

Data: < > CD CD CD CD CD CD CD CD CD CD —— 遭泄露的块中的数据,一般只显示前16个字节

上述内存泄漏报告确实给出了不少信息,但很重要的一点却没有给出,那就是发生泄漏的代码位置,毕竟我们需要据此来改善代码。

获取更详细的内存泄漏报告

对于堆函数(例如 malloc、 free、 callocrealloc、 new 和 delete)都有其对应的调试版本,如果在检测内存泄漏的过程中使用这些堆函数的调试版本,则可以输出更详细的内存泄漏报告。这并不难。

通过定义宏_CRTDBG_MAP_ALLOC可以使malloc,free等函数映射到它们的调试版本。

对于C++中的new和delete操作符,稍麻烦点,需要重新定义 new 才能在内存泄漏报告中看到文件和行号。

#ifdef _DEBUG

#ifndef DBG_NEW

#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )

#define new DBG_NEW

#endif

#endif // _DEBUG

示例:

#define _CRTDBG_MAP_ALLOC
#include "stdlib.h"
#include <crtdbg.h>

#ifdef _DEBUG
    #ifndef DBG_NEW
        #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
        #define new DBG_NEW
    #endif
#endif  // _DEBUG
int main()
{
    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF |         \            _CRTDBG_LEAK_CHECK_DF );
    char *p =new char[30];
    char *p1=(char *)malloc(sizeof(char)*10);
//    _CrtDumpMemoryLeaks();
    return 0;
}

输出结果:

clip_image005

至此,已经足够满足我们基本的需求了。

需要注意的一点是,宏_CRTDBG_MAP_ALLOC的定义必须放在include< crtdbg.h >之前,这表示在编译crtdbg.h时,会根据这个宏来选择编译上述堆函数的Debug版本。


锁定内存泄漏位置的另一个办法是在内存分配编号上设置断点。

1. 在应用程序的起点附近设置断点,然后启动应用程序。

2. 当应用程序在断点处中断时,会出现 “监视”窗口。

3. 在 “监视”窗口中,在 “名称”列中键入 _crtBreakAlloc。

4. 如果要使用 CRT 库的多线程 DLL 版本(/MD 选项),请加入上下文运算符: {,,msvcr100d.dll}_crtBreakAlloc(针对vs2010是msvcr100d.dll,其他版本的环境找对应的dll)

5. 在 “值”列中,将显示的值替换为要在其位置中断的内存分配的分配编号。

clip_image006

clip_image008

与其相似的另一个做法是在代码中设置内存分配断点。具体做法时在代码的起始位置附近,调用如下函数(其实是个宏):

_CrtSetBreakAlloc(61);

参数61表示内存分配的分配编号,可由上述简单的使用获得。

这一下,我们获得了更多详细的报告——调用堆栈,可以更准确的锁定发生内存泄漏的位置。但这种方法的不足之处是,对于在多线程代码中,由于每次调试时的内存分配编号都会变化,就没法使用这种办法了。


定位内存泄漏的另一种技术涉及在关键点对应用程序的内存状态拍快照。

若要为应用程序中给定点的内存状态拍快照,请创建 _CrtMemState 结构,将它传递给 _CrtMemCheckpoint 函数。

该函数用当前内存状态的快照填充此结构:

_CrtMemState s1;

_CrtMemCheckpoint( &s1 );

若要输出 _CrtMemState 结构的内容,请将该结构传递给 _ CrtMemDumpStatistics 函数:

_CrtMemDumpStatistics( &s1 );

若要确定在某个代码部分中是否发生了内存泄漏,可以对这部分之前和之后的内存状态拍快照,然后使用 _CrtMemDifference 比较两个状态:

_CrtMemCheckpoint( &s1 );

// memory allocations take place here

_CrtMemCheckpoint( &s2 );

if ( _CrtMemDifference( &s3, &s1, &s2) )

_CrtMemDumpStatistics( &s3 );

示例代码

#include "stdlib.h"
#include <crtdbg.h>
#include <Windows.h>
int main()
{
    _CrtMemState s1;
    _CrtMemState s4;
    _CrtMemCheckpoint( &s1 );
    OutputDebugString("第一次内存快照\n");
    _CrtMemDumpStatistics(&s1);
    char *p =new char[30];
    _CrtMemState s2;
    _CrtMemCheckpoint( &s2 );
    OutputDebugString("第二次内存快照\n");
    _CrtMemDumpStatistics(&s2);
    if ( _CrtMemDifference( &s4, &s1, &s2) )
    {    
        OutputDebugString("前两次内存快照的差异\n");
        _CrtMemDumpStatistics( &s4 );
    }
    char *p1=(char *)malloc(sizeof(char)*10);
    _CrtMemState s3;
    _CrtMemCheckpoint( &s3 );
    OutputDebugString("第三次内存快照\n");
    _CrtMemDumpStatistics(&s3);
    if ( _CrtMemDifference( &s4, &s1, &s3) )
    {    
        OutputDebugString("首尾内存快照的差异\n");
        _CrtMemDumpStatistics( &s4 );
    }
    return 0;
}

clip_image011
普通块是由程序分配的普通内存。

客户端块是由 MFC 程序用于需要析构函数的对象的特殊类型内存块。

MFC new 运算符根据正在创建的对象的需要创建普通块或客户端块。

“CRT 是由 CRT 库为自己使用而分配的内存块。

CRT 库可处理这些块的释放。

因此,不大可能在内存泄漏报告中看到这些块,除非出现严重错误(例如 CRT 库损坏)。

内存泄漏报告中绝对不会出现另外两个内存块类型。

可用块是已释放的内存。

也就是说,根据定义,这种块不会泄漏。

忽略块是已明确标记、不出现在内存泄漏报告中的块。

寻找内存泄漏的一个方法是,首先在应用程序的开头和结尾部分放置 _CrtMemCheckpoint 调用,然后使用 _CrtMemDifference 比较两个结果。

如果 _CrtMemDifference 显示有内存泄漏,可以添加更多 _CrtMemCheckpoint 来进一步找到泄漏源。

这个方法看起来真够笨拙的,不过它或许有别的好处吧。谁知道呢?


使用第三方工具

或许有人会嫌上述方法都太麻烦了,那不妨试试第三方工具,目前用于检测内存泄漏的第三方工具可谓多种多样,这里只推荐一款用与vs环境的第三方插件:Visual Leak Detector ,推荐理由:开源,免费。

下载地址:http://vld.codeplex.com/releases

目前已更新版本至v2.4rc2

使用方法及效果如下:

clip_image013

其调用堆栈的分析在控制台应用上并不十分准确,忽略了我们最关注的源代码文件中调用位置。不过其在MFC中的表现还不赖。

以上源自 http://blog.csdn.net/yapingxin/article/details/6751940,话说这个blog里确实有很多好东西。

posted @ 2014-05-07 17:27  星辰风  阅读(10307)  评论(0编辑  收藏  举报