[微软标准做法] Visual Studio使用 CRT 库查找C++内存泄漏

原文出处

微软官方参考
https://msdn.microsoft.com/zh-cn/library/x98tx3cf.aspx
https://msdn.microsoft.com/zh-cn/library/974tc9t1.aspx

个人参考
http://www.cppblog.com/weiym/archive/2013/02/25/198072.html

    根据下面的学习,我定义了一个自己的MemoryDtect的头文件,方便以后项目的使用,以后有任务,我会不断完善。
/*************************************************************************************************/  
/*! 
    \file VSMemoryLeakDebug.h.h 
 
    \ Author      : Jacky Dai
    \ Mail        : [email protected]
    \ QQ          : 229696880
    \ Version     : Ver 1.0.0.0
    \ Date        : 2016-07-12
    \ Copyright   : 2013~2016 All Rights Reserved.
    \ Description : Import Virtual Studio CRT memory leaks debugging model into a file and supply for DEMO
    \ Reference   : http://jacky-dai.iteye.com/admin/blogs/2310683
*/  
/*************************************************************************************************/ 
#ifndef  VS_MEMORY_LEAK_DEBUG_H
#define  VS_MEMORY_LEAK_DEBUG_H

#ifdef _DEBUG
    //First of all loads function into project if the compile model is DEBUG
    //We can not change the orders of following two definitions
    //////////////////////////////////////////////////////////////////////////
    #define _CRTDBG_MAP_ALLOC
    #include <crtdbg.h>

    //If program uses new operation of C++, we should redefine it to CRT type to catch the memory,
    //so we can finally see the file name and file number
    //////////////////////////////////////////////////////////////////////////
    #ifndef DBG_NEW   
        #define DBG_NEW new(_NORMAL_BLOCK , __FILE__ , __LINE__ )
        #define new DBG_NEW
    #endif

    //Samples for usage
    /*************************************************************************************************/ 
    //Sample A: There is only one entry of APP, we just need to call _CrtDumpMemoryLeaks() before exist.
    //void UniqueEntry()
    //{
    //    Dothings();
    //    ......
    //    _CrtDumpMemoryLeaks();//Please wait for a momnet, I should call it befor exit
    //}

    /*************************************************************************************************/ 
    //Sample B: There are more than one entrys of APP, we should call _CrtSetDbgFlag() for each entry.
    //void EntryA()
    //{
    //    Dothings();
    //    ......
    //    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    //}
    //void EntryB()
    //{
    //    Dothings();
    //    ......
    //    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    //}

    /*************************************************************************************************/
    //Sample C: Compare memory changes between any block, it is a good way to detect memory leak of blocks.
    //We use _CrtMemCheckpoint / _CrtMemDifference /_CrtMemDumpStatistics
    //void FuactionAPI()
    //{
    //    _CrtMemState s1, s2, s3;
    //    _CrtMemCheckpoint( &s1 );
    //    ......
    //    Dothings();// memory allocations take place here
    //    ......
    //    _CrtMemCheckpoint( &s2 );
    //
    //    if ( _CrtMemDifference( &s3, &s1, &s2) )
    //    {
    //         printf("%d",s3.lTotalCount);
    //         _CrtMemDumpStatistics( &s3 );
    //    }
    //}

    /*************************************************************************************************/ 
    //Sample D:reinterpret position, TODO: I am not familiar with it, add it later
    //_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );  

#endif

#endif//VS_MEMORY_LEAK_DEBUG_H



    内存泄漏,即未能正确释放以前分配的内存,是 C/C++ 应用程序中最难以捉摸也最难以检测到的 Bug 之一。 最初少量内存泄漏可能不引人注目,但随着时间的推移,内存泄漏越来越多,就会出现一些征兆,包括性能下降,在应用程序内存不足时发生崩溃。 更严重的是,占用了所有可用内存的泄漏应用程序可能会导致其他应用程序崩溃,从而无法确定问题出在哪个应用程序。 即使看似无害的内存泄漏也可能说明存在其他问题应当纠正。
   
    借助 Visual Studio 调试器和 C 运行时 (CRT) 库,可以检测和识别内存泄漏。

启用内存泄漏检测

    检测内存泄漏的主要工具是调试器和 C 运行库 (CRT) 调试堆函数。
    若要启用调试堆函数,请在程序中包括以下语句:
    #define _CRTDBG_MAP_ALLOC
    #include <stdlib.h> 
    #include <crtdbg.h>


    为了 CRT 函数能够正常工作,#include 语句必须遵循此处所示的顺序。
包含 crtdbg.h,将 malloc 和 free 函数映射到它们的调试版本,即 _malloc_dbg 和 free,它们将跟踪内存分配和释放。 此映射只在包含 _DEBUG 的调试版本中发生。 发布版本使用普通的 malloc 和 free 函数。
    #define 语句将 CRT 堆函数的基础版本映射到对应的调试版本。 如果省略 #define 语句,内存泄漏转储将有所简化。
    使用这些语句启用调试堆函数之后,可以在 某个应用程序退出点之前设置一个对 _CrtDumpMemoryLeaks 的调用,以便在应用程序退出时显示内存泄漏报告:
_CrtDumpMemoryLeaks();


    如果 应用程序有多个退出点,并不需要在每个退出点都手动设置一个对 _CrtDumpMemoryLeaks 的调用。 应用程序开头部分对 _CrtSetDbgFlag 的调用会导致在每个退出点自动调用 _CrtDumpMemoryLeaks。 你必须设置两个位域,如下所示:
    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF )
;

    默认情况下,_CrtDumpMemoryLeaks 将内存泄漏报告输出到“输出”窗口的“调试”窗格中。 你可以使用 _CrtSetReportMode 将该报 告重定向到其他位置
如果使用库,该库可能会将输出重置到另一位置。 在此情况下,可以将输出位置设置回“输出”窗口,如下所示:
_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );



解释内存泄漏报告

    如果应用程序未定义 _CRTDBG_MAP_ALLOC,则 _CrtDumpMemoryLeaks 显示的内存泄漏报告如下所示:
引用
Detected memory leaks! Dumping objects -> {18} normal block at 0x00780E80, 64 bytes long.
Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete.


    如果应用程序定义了 _CRTDBG_MAP_ALLOC,则显示的内存泄漏报告如下所示:

引用
Detected memory leaks! Dumping objects -> C:\PROGRAM FILES\VISUAL STUDIO\MyProjects\leaktest\leaktest.cpp(20) : {18} normal block at 0x00780E80, 64 bytes long.
Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete.

    区别在于,第二份报告显示文件名,以及泄漏的内存初次分配所在位置的行号。
    不论是否定义 _CRTDBG_MAP_ALLOC,内存泄漏报告都显示以下信息:
    内存分配编号,在本例中为 18
    块类型,在本例中为 normal。
    十六进制内存位置,在本例中为 0x00780E80。
    块的大小,在本例中为 64 bytes。
    块中前 16 个字节的数据(十六进制形式)。
    内存泄漏报告将内存块标识为普通、客户端或 CRT。“普通块”是由程序分配的普通内存。“客户端块”是由 MFC 程序用于需要析构函数的对象的特殊类型内存块。 MFC new 运算符根据正在创建的对象的需要创建普通块或客户端块。“CRT 块”是由 CRT 库为自己使用而分配的内存块。 CRT 库可处理这些块的释放。 因此,你不大可能在内存泄漏报告中看到这些块,除非出现严重错误(例如 CRT 库损坏)。
内存泄漏报告中绝对不会出现另外两个内存块类型。“可用块”是已释放的内存。 也就是说,根据定义,这种块不会泄漏。“忽略块”是已明确标记、不出现在内存泄漏报告中的块。
这些方法适用于使用标准 CRT malloc 函数分配的内存。 不过,如果程序使用 C++ new 运算符分配内存,则需要重新定义 new 才能在内存泄漏报告中看到文件和行号。 你可以利用如下所示的代码块实现:
#ifdef _DEBUG 
    #ifndef DBG_NEW 
        #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ ) 
        #define new DBG_NEW 
    #endif 
#endif  // _DEBUG


    在内存分配编号上设置断点
    如果分配了泄漏内存块,内存分配编号会通知你。 例如,内存分配编号为 18 的块就是在应用程序运行过程中所分配的第 18 个内存块。 CRT 报告包含运行过程中的所有内存块分配情况。 其中包括 CRT 库和其他库(如 MFC)的分配情况。 因此,内存分配编号为 18 的块可能不是你的代码所分配的第 18 个内存块。 通常,二者并不一致。
    可以使用分配编号在内存分配位置设置断点。
    使用“监视”窗口设置内存分配断点
    在应用程序的起点附近设置断点,然后启动应用程序。
   当应用程序在断点处中断时,会出现“监视”窗口。
    在“监视”窗口中,在“名称”列中键入 _crtBreakAlloc。
如果要使用 CRT 库的多线程 DLL 版本(/MD 选项),请加入上下文运算符:
{,,ucrtbased.dll}_crtBreakAlloc

    按“Return”。
    调试器将计算调用,并将结果放入“值”列。 如果没有在内存分配上设置任何断点,该值将为 –1。
    在“值”列中,将显示的值替换为要在其位置中断的内存分配的分配编号。
在某个内存分配编号设置断点之后,你可以继续调试。 请注意在与以前运行相同的条件下运行程序,以便内存分配顺序不会更改。 当程序在指定的内存分配处中断时,可以使用“调用堆栈”窗口和其他调试器窗口来确定分配内存时的情况。 然后,可以继续执行程序以观察对象发生什么情况,并确定未正确释放对象的原因。
     在对象上设置数据断点可能也有帮助。 有关详细信息,请参阅 使用断点。
你也可以在代码中设置内存分配断点。 有两种方法可以实现此目的:
引用
_crtBreakAlloc = 18;

或:
引用
_CrtSetBreakAlloc(18);


比较内存状态

    定位内存泄漏的另一种技术涉及在关键点对应用程序的内存状态拍快照。 若要为应用程序中给定点的内存状态拍快照,请创建 _CrtMemState 结构,将它传递给 _CrtMemCheckpoint 函数。 该函数用当前内存状态的快照填充此结构:
_CrtMemState s1; 
_CrtMemCheckpoint( &s1 );


_CrtMemCheckpoint 会将当前内存状态填充在该结构中。
若要输出 _CrtMemState 结构的内容,请将该结构传递给 _ CrtMemDumpStatistics 函数:
_CrtMemDumpStatistics( &s1 );


_ CrtMemDumpStatistics 输出内存状态转储,如下所示:
引用

0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
3071 bytes in 16 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 3071 bytes.
Total allocations: 3764 bytes.


若要确定在某个代码部分中是否发生了内存泄漏,可以对这部分之前和之后的内存状态拍快照,然后使用 _ CrtMemDifference 比较两个状态:
_CrtMemState s1, s2, s3;
_CrtMemCheckpoint( &s1 ); 
// memory allocations take place here
_CrtMemCheckpoint( &s2 ); 

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

_CrtMemDifference 比较内存状态 s1 和 s2,在 (s3) 中返回结果,即 s1 与 s2 的差异。
寻找内存泄漏的一个方法是,首先在应用程序的开头和结尾部分放置 _CrtMemCheckpoint 调用,然后使用 _CrtMemDifference 比较两个结果。 如果 _CrtMemDifference 显示有内存泄漏,你可以添加更多 _CrtMemCheckpoint 调用来使用二进制搜索划分程序,直至找到泄漏源。

误报

    在某些情况下,_CrtDumpMemoryLeaks 可能给出错误的内存泄漏指示。 如果使用将内部分配标记为 _NORMAL_BLOCK 而不是 _CRT_BLOCK 或 _CLIENT_BLOCK 的库,则可能发生这种情况。 在这种情况下,_CrtDumpMemoryLeaks 无法区分用户分配和内部库分配。 如果在 _CrtDumpMemoryLeaks 调用点之后运行库分配的全局析构函数,则每个内部库分配都会报告为内存泄漏。 在 Visual Studio .NET 之前的早期版本的标准模板库会导致 _CrtDumpMemoryLeaks 出现这种误报,在最近的版本中,已经解决了这一问题。


CRT 调试堆详细信息

利用调试堆查找缓冲区溢出
    程序员遇到的两种最常见而又难处理的问题是,覆盖已分配缓冲区的末尾以及内存泄漏(未能在不再需要某些分配后将其释放)。调试堆提供功能强大的工具来解决这类内存分配问题。
    堆函数的“Debug”版本调用“Release”版本中使用的标准版本或基版本。当请求内存块时,调试堆管理器从基堆分配略大于所请求的块的内存块,并返回指向该块中属于您的部分的指针。例如,假定应用程序包含调用:malloc( 10 )。在发行版本中,malloc 将调用基堆分配例程以请求分配 10 个字节。而在调试版本中,malloc 将调用 _malloc_dbg,该函数接着调用基堆分配例程以请求分配 10 个字节加上大约 36 个字节的额外内存。调试堆中产生的所有内存块在单个链接列表中连接起来,按照分配时间排序。
    调试堆例程分配的附加内存的用途为:存储簿记信息,存储将调试内存块链接在一起的指针,以及形成数据两侧的小缓冲区(用于捕捉已分配区域的覆盖)。
    当前,用于存储调试堆的簿记信息的块头结构在 DBGINT.H 头文件中声明如下:
typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
    struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
    struct _CrtMemBlockHeader *pBlockHeaderPrev;
    char *szFileName;    // File name
    int nLine;           // Line number
    size_t nDataSize;    // Size of user block
    int nBlockUse;       // Type of block
    long lRequest;       // Allocation number
// Buffer just before (lower than) the user's memory:
    unsigned char gap[nNoMansLandSize];
} _CrtMemBlockHeader;

/* In an actual memory block in the debug heap,
 * this structure is followed by:
 *   unsigned char data[nDataSize];
 *   unsigned char anotherGap[nNoMansLandSize];
 */


    该块的用户数据区域两侧的 NoMansLand 缓冲区当前大小为 4 个字节,并用调试堆例程所使用的已知字节值填充,以验证尚未覆盖用户内存块限制。调试堆还用已知值填充新的内存块。如果选择在堆的链接列表中保留已释放块(如下文所述),则这些已释放块也用已知值填充。当前,所用的实际字节值如下所示:
NoMansLand (0xFD)
    应用程序所用内存两侧的“NoMansLand”缓冲区当前用 0xFD 填充。
已释放块 (0xDD)
    设置 _CRTDBG_DELAY_FREE_MEM_DF 标志后,调试堆的链接列表中保留未使用的已释放块 当前用 0xDD 填充。
新对象 (0xCD)

    分配新对象时,这些对象用 0xCD 填充。


调试堆中的块类型
    调试堆中的每个内存块都分配以五种分配类型之一。出于泄漏检测和状态报告目的对这些类型进行不同地跟踪和报告。可以指定块的类型,方法是使用对其中一个调试堆分配函数(如 _malloc_dbg)的直接调用来分配块。调试堆中的五种内存块类型(在 _CrtMemBlockHeader 结构的 nBlockUse 成员中设置)如下所示:

_NORMAL_BLOCK
    对 malloc 或 calloc 的调用将创建“普通”块。如果打算只使用“普通”块而不需要“客户端”块,则可能想要定义 _CRTDBG_MAP_ALLOC,它导致所有堆分配调用映射到它们在“Debug”版本中的调试等效项。这将允许将关于每个分配调用的文件名和行号信息存储到对应的块头中。

_CRT_BLOCK
    由许多运行库函数内部分配的内存块被标记为 CRT 块,以便可以单独处理这些块。结果,泄漏检测和其他操作不需要受这些块影响。分配永不可以分配、重新分配或释放任何 CRT 类型的块。

_CLIENT_BLOCK
     出于调试目的,应用程序可以专门跟踪一组给定的分配,方法是使用对调试堆函数的显式调用将它们作为该类型的内存块进行分配。例如,MFC 以“客户端”块类型分配所有的 CObjects;其他应用程序则可能在“客户端”块中保留不同的内存对象。还可以指定“客户端”块的子类型以获得更大的跟踪粒度。若要指定“客户端”块子类型,请将该数字向左移 16 位,并将它与 _CLIENT_BLOCK 进行 OR 运算。例如:
#define MYSUBTYPE 4
freedbg(pbData, _CLIENT_BLOCK|(MYSUBTYPE<<16));

    客户端提供的挂钩函数(用于转储在“客户端”块中存储的对象)可以使用 _CrtSetDumpClient 进行安装,然后,每当调试函数转储“客户端”块时均会调用该挂钩函数。同样,对于调试堆中的每个“客户端”块,可以使用 _CrtDoForAllClientObjects 来调用应用程序提供的给定函数。

_FREE_BLOCK
    通常,所释放的块将从列表中移除。为了检查并未仍在向已释放的内存写入数据,或为了模拟内存不足情况,可以选择在链接列表上保留已释放块,将其标记为“可用”,并用已知字节值(当前为 0xDD)填充。

_IGNORE_BLOCK
    有可能在一段时间内关闭调试堆操作。在该时间段内,内存块保留在列表上,但被标记为“忽略”块。
    若要确定给定块的类型和子类型,请使用 _CrtReportBlockType 函数以及 _BLOCK_TYPE 和 _BLOCK_SUBTYPE 宏。宏的定义(在 crtdbg.h 中)如下所示:
#define _BLOCK_TYPE(block)          (block & 0xFFFF)
#define _BLOCK_SUBTYPE(block)       (block >> 16 & 0xFFFF)



检查堆完整性和内存泄漏
     许多调试堆功能必须从代码内访问。下一节描述其中一些功能以及如何使用这些功能。

_CrtCheckMemory
    例如,可以使用对 _CrtCheckMemory 的调用来检查堆在任意点的完整性。该函数检查堆中的每个内存块,验证内存块头信息有效,并确认尚未修改缓冲区。

_CrtSetDbgFlag
    可以使用内部标志 _crtDbgFlag 来控制调试堆跟踪分配的方式,该标志可使用 _CrtSetDbgFlag 函数进行读取和设置。通过更改该标志,可以指示调试堆在程序退出时检查内存泄漏,并报告检测到的所有泄漏。类似地,可以指定不将已释放的内存块从链接列表移除,以模拟内存不足情况。当检查堆时,将完全检查这些已释放的块,以确保它们未受打扰。
_crtDbgFlag 标志包含下列位域:


位域                          默认Value        说明
_CRTDBG_ALLOC_MEM_DF           On          打开调试分配。当该位为 off 时,分配仍链接在一起,但它们的块类型为 _IGNORE_BLOCK。
_CRTDBG_DELAY_FREE_MEM_DF     Off          防止实际释放内存,与模拟内存不足情况相同。当该位为 on 时,已释放块保留在调试堆的链接列表中,但标记为 _FREE_BLOCK,并用特殊字节值填充。
_CRTDBG_CHECK_ALWAYS_DF        Off         导致每次分配和释放时均调用 _CrtCheckMemory。这将减慢执行,但可快速捕捉错误。
_CRTDBG_CHECK_CRT_DF           Off         导致将标记为 _CRT_BLOCK 类型的块包括在泄漏检测和状态差异操作中。当该位为 off 时,在这些操作期间将忽略由运行库内部使用的内存。
_CRTDBG_LEAK_CHECK_DF          Off          导致在程序退出时通过调用 _CrtDumpMemoryLeaks 来执行泄漏检查。如果应用程序未能释放其所分配的所有内存,将生成错误报告。


配置调试堆
    对于堆函数(例如 malloc、free、calloc、realloc、new 和 delete)的所有调用均解析为这些函数在调试堆中运行的调试版本。当释放内存块时,调试堆自动检查已分配区域两侧的缓冲区的完整性,如果发生覆盖,将发出错误报告。

     使用调试堆
    用 C 运行库的调试版本链接应用程序的调试版本。
    更改一个或多个 _crtDbgFlag 位域并创建标志的新状态
    在 newFlag 参数设置为 _CRTDBG_REPORT_FLAG 的情况下调用 _CrtSetDbgFlag(以获取当前的 _crtDbgFlag 状态),并在一个临时变量中存储返回值。
    通过对带相应位屏蔽的临时变量(在应用程序代码中由清单常数显示)进行 OR 运算(按位|符号)来打开任何位。
    通过对带有相应位屏蔽的 NOT(按位 ~ 符号)的变量进行 AND-ing(按位 & 符号)运算关闭其他位。
    在 newFlag 参数设置为临时变量中存储的值的情况下调用 _CrtSetDbgFlag,以创建 _crtDbgFlag 的新状态。
例如,下列代码行打开自动泄漏检测,关闭检查 _CRT_BLOCK 类型的块:
// Get current flag
int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );

// Turn on leak-checking bit.
tmpFlag |= _CRTDBG_LEAK_CHECK_DF;

// Turn off CRT block checking bit.
tmpFlag &= ~_CRTDBG_CHECK_CRT_DF;

// Set flag to the new value.
_CrtSetDbgFlag( tmpFlag );


C++ 调试堆中的 new、delete 和 _CLIENT_BLOCK
    C 运行库的调试版本包含 C++ new 和 delete 运算符的调试版本。如果使用 _CLIENT_BLOCK 分配类型,则必须直接调用 new 运算符的调试版本,或者创建可在调试模式中替换 new 运算符的宏,如下面的示例所示:
/* MyDbgNew.h
 Defines global operator new to allocate from
 client blocks
*/

#ifdef _DEBUG
   #define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
   #define DEBUG_CLIENTBLOCK
#endif // _DEBUG


/* MyApp.cpp
        Use a default workspace for a Console Application to
 *      build a Debug version of this code
*/

#include "crtdbg.h"
#include "mydbgnew.h"

#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif

int main( )   {
    char *p1;
    p1 =  new char[40];
    _CrtMemDumpAllObjectsSince( NULL );
}


    delete 运算符的“Debug”版本可用于所有块类型,并且编译“Release”版本时程序中不需要任何更改。


堆状态报告函数
_CrtMemState
    若要捕获给定时刻堆状态的摘要快照,请使用 CRTDBG.H 中定义的 _CrtMemState 结构:
typedef struct _CrtMemState
{
    // Pointer to the most recently allocated block:
    struct _CrtMemBlockHeader * pBlockHeader;
    // A counter for each of the 5 types of block:
    size_t lCounts[_MAX_BLOCKS];
    // Total bytes allocated in each block type:
    size_t lSizes[_MAX_BLOCKS];
    // The most bytes allocated at a time up to now:
    size_t lHighWaterCount;
    // The total bytes allocated at present:
    size_t lTotalCount;
} _CrtMemState;

    该结构保存指向调试堆的链接列表中的第一个(最近分配的)块的指针。然后,它在两个数组中记录列表中每种类型的内存块(_NORMAL_BLOCK、_CLIENT_BLOCK、_FREE_BLOCK 等等)的个数,以及每种类型的块中分配的字节数。最后,它记录到该点为止堆中总共分配的最大字节数以及当前分配的字节数。
    其他 CRT 报告函数
    下列函数报告堆的状态和内容,并使用这些信息帮助检测内存泄漏及其他问题:


函数                            说明
_CrtMemCheckpoint           在应用程序提供的 _CrtMemState 结构中保存堆的快照。
_CrtMemDifference           比较两个内存状态结构,在第三个状态结构中保存二者之间的差异,如果两个状态不同,则返回 TRUE。
_CrtMemDumpStatistics       转储给定的 _CrtMemState 结构。该结构可能包含给定时刻调试堆状态的快照或两个快照之间的差异。
_CrtMemDumpAllObjectsSince  转储有关在堆的给定快照之后,或是从执行开始时起,分配的所有对象的信息。如果已使用 _CrtSetDumpClient 安装挂钩函数,则每次它转储 _CLIENT_BLOCK 块时,都会调用应用程序所提供的挂钩函数。
_CrtDumpMemoryLeaks         确定自程序开始执行以来是否发生过内存泄漏,如果发生过,则转储所有已分配对象。如果已使用 _CrtSetDumpClient 安装挂钩函数,则每次 _CrtDumpMemoryLeaks         转储 _CLIENT_BLOCK 块时,都会调用应用程序所提供的挂钩函数。


跟踪堆分配请求
    尽管查明在其中执行断言或报告宏的源文件名和行号对于定位问题原因常常很有用,对于堆分配函数却可能不是这样。虽然可在应用程序的逻辑树中的许多适当点插入宏,但分配经常隐藏在特殊例程中,该例程会在很多不同时刻从很多不同位置进行调用。问题通常并不在于如何确定哪行代码进行了错误分配,而在于如何确定该行代码进行的上千次分配中的哪一次是错误分配以及原因。

    唯一分配请求编号和 _crtBreakAlloc
标识发生错误的特定堆分配调用的最简单方法是利用与调试堆中的每个块关联的唯一分配请求编号。当其中一个转储函数报告某块的有关信息时,该分配请求编号将括在大括号中(例如“{36}”)。
    知道某个错误分配块的分配请求编号后,可以将该编号传递给 _CrtSetBreakAlloc 以创建一个断点。执行将恰在分配该块以前中断,您可以向回追踪以确定哪个例程执行了错误调用。为避免重新编译,可以在调试器中完成同样的操作,方法是将 _crtBreakAlloc 设置为所感兴趣的分配请求编号。

    创建分配例程的“Debug”版本
略微复杂的方法是创建您自己的分配例程的“Debug”版本,等同于堆分配函数的 _dbg 版本。然后,可以将源文件和行号参数传递给基础堆分配例程,并能立即看到错误分配的出处。
   
    例如,假定您的应用程序包含与下面类似的常用例程:
int addNewRecord(struct RecStruct * prevRecord,
                 int recType, int recAccess)
{
    // ...code omitted through actual allocation... 
    if ((newRec = malloc(recSize)) == NULL)
    // ... rest of routine omitted too ... 
}


    在头文件中,可以添加如下代码:
#ifdef _DEBUG
#define  addNewRecord(p, t, a) \
            addNewRecord(p, t, a, __FILE__, __LINE__)
#endif
接下来,可以如下更改记录创建例程中的分配:
int addNewRecord(struct RecStruct *prevRecord,
                int recType, int recAccess
#ifdef _DEBUG
               , const char *srcFile, int srcLine
#endif
    )
{
    /* ... code omitted through actual allocation ... */
    if ((newRec = _malloc_dbg(recSize, _NORMAL_BLOCK,
            srcFile, scrLine)) == NULL)
    /* ... rest of routine omitted too ... */
}

    在其中调用 addNewRecord 的源文件名和行号将存储在产生的每个块中(这些块是在调试堆中分配的),并将在检查该块时进行报告。


以下是我的测试代码:

Sample1
#ifndef  SAMPLE1_H
#define  SAMPLE1_H

//Link:http://www.cnblogs.com/zouzf/p/4152279.html


//可以定位到发生内存泄露 所在的文件和具体那一行,用于检测 malloc 分配的内存
#define _CRTDBG_MAP_ALLOC 
#include <stdlib.h>
#include <crtdbg.h>

//把分配内存的信息保存下来,可以定位到那一行发生了内存泄露。用于检测 new 分配的内存
#ifdef _DEBUG
#define new   new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif


void Sample1_Test();

#endif//SAMPLE1_H

#include "Sample1.h"

//有用
inline void EnableMemLeakCheck()
{
    //该语句在程序退出时自动调用 _CrtDumpMemoryLeaks(),用于多个退出出口的情况.
    //如果只有一个退出位置,可以在程序退出之前调用 _CrtDumpMemoryLeaks()
    _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
}


void Sample1_Test(void)
{

    EnableMemLeakCheck();

    //运行到 第 191 次 内存分配的时候停下来
    //_CrtSetBreakAlloc(191);

    char* p = new char[100];
    char* p1 = new char[200];
    char* p2 = (char*)malloc(600);
    delete p;

    //_CrtDumpMemoryLeaks();//这个代码好像会输出额外多余的内存分配信息
}

引用

Detected memory leaks!
Dumping objects ->
c:\users\jacky_dai\desktop\visualstudiodetectmemoryleak\visualstudiodetectmemoryleak\sample1.cpp(22) : {68} normal block at 0x006315A0, 600 bytes long.
Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
c:\users\jacky_dai\desktop\visualstudiodetectmemoryleak\visualstudiodetectmemoryleak\sample1.cpp(21) : {67} normal block at 0x00631498, 200 bytes long.
Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.


Sample2
#ifndef  SAMPLE2_H
#define  SAMPLE2_H

//Link:http://www.cnblogs.com/huhuuu/p/3576710.html


#define _CRTDBG_MAP_ALLOC //一定要加上这一句
#include <stdlib.h>
#include <crtdbg.h>

void Sample2_Test();

#endif//SAMPLE1_H

#include "Sample2.h"

#include <iostream>
using namespace std;

_CrtMemState s1, s2, s3;

void GetMemory(char *p, int num)
{
    p = (char*)malloc(sizeof(char) * num);
}

void Sample2_Test()
{
    //令外,还有个监控某片代码段是否有内存泄露的,如下。如果有这段代码有泄露,_CrtMemDifference就会比较出来,
    //然后 _CrtMemDumpStatistics输出相关信息。输出的信息没有具体的定位到具体文件具体哪一行,但有时还是有些辅助作用的吧

    _CrtMemCheckpoint( &s1 ); //检测当前内存的使用情况
    char *str = NULL;
    //int n=1000;
    GetMemory(str, 100);//这里申请后没有释放内存,内存泄露了

    _CrtMemCheckpoint( &s2 ); 
    _CrtMemDifference( &s3, &s1, &s2); //比较s1,s2的内存差异,结果放s3

    if ( _CrtMemDifference( &s3, &s1, &s2) )
    {
        printf("%d",s3.lTotalCount);
        _CrtMemDumpStatistics( &s3 );
    }

}

引用

0 bytes in 0 Free Blocks.
100 bytes in 1 Normal Blocks.
0 bytes in 0 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 0 bytes.
Total allocations: 100 bytes.


Sample 3
#ifndef  SAMPLE3_H
#define  SAMPLE3_H

//Link:http://www.cnblogs.com/huhuuu/p/3576710.html

#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

void Sample3_Test();

#endif//SAMPLE1_H

#include "Sample2.h"

#include <iostream>
using namespace std;

void Sample3_Test()
{
    int* p = new int(); 
    int n=5;
    while(n--){
        new int;
    }

    _CrtDumpMemoryLeaks();
}

引用

Dumping objects ->
{71} normal block at 0x00081538, 4 bytes long.
Data: <    > CD CD CD CD
{70} normal block at 0x000814F8, 4 bytes long.
Data: <    > CD CD CD CD
{69} normal block at 0x000814B8, 4 bytes long.
Data: <    > CD CD CD CD
{68} normal block at 0x00081478, 4 bytes long.
Data: <    > CD CD CD CD
{67} normal block at 0x00081438, 4 bytes long.
Data: <    > CD CD CD CD
{66} normal block at 0x000813F8, 4 bytes long.
Data: <    > 00 00 00 00
Object dump complete.



#include "Sample1.h"
#include "Sample2.h"
#include "Sample3.h"

void main()
{
    Sample1_Test();
    Sample2_Test();
    Sample3_Test();
}

猜你喜欢

转载自jacky-dai.iteye.com/blog/2310683