VISUAL STUDIO 检测内存泄露 (FROM MSDN)

VISUAL STUDIO 检测内存泄露 (FROM MSDN)

2006-12-21 22:28:58

Visual Studio  

在 MFC 中检测内存泄漏

跟踪内存分配

讨论 DEBUG_NEW 宏,可以使用它来定位内存泄漏。信息包括“Debug”和“Release”版本中的注意事项、如何在源文件中定义宏和对象转储。

在 MFC 中,可以使用 DEBUG_NEW 宏代替 new 运算符来帮助定位内存泄漏。在程序的“Debug”版本中,DEBUG_NEW 将为所分配的每个对象跟踪文件名和行号。当编译程序的“Release”版本时,DEBUG_NEW 将解析为不包含文件名和行号信息的简单 new 操作。因此,在程序的“Release”版本中不会造成任何速度损失。

如果不想重写整个程序来使用 DEBUG_NEW 代替 new,则可以在源文件中定义下面的宏:

#define new DEBUG_NEW
当进行对象转储时,用 DEBUG_NEW 分配的每个对象均将显示被分配到的文件和行号,使您可以查明内存泄漏源。

MFC 框架的“Debug”版本自动使用 DEBUG_NEW,但代码不自动使用它。如果希望利用 DEBUG_NEW 的好处,则必须显式使用 DEBUG_NEW 或 #define new,如上所示。

Visual Studio  

启用内存诊断
必须先启用诊断跟踪,然后才能使用内存诊断功能。

启用或禁用内存诊断

调用全局函数 AfxEnableMemoryTracking 来启用或禁用诊断内存分配器。由于默认情况下内存诊断在调试库中是打开的,所以通常会使用该函数暂时关闭内存诊断,这会提高程序执行速度并减少诊断输出。
使用 afxMemDF 选择特定内存诊断功能

如果希望对内存诊断功能进行更精确的控制,可以通过设置 MFC 全局变量 afxMemDF 的值,来有选择地打开和关闭单个内存诊断功能。该变量可以具有下列值(由枚举类型 AfxMemDF 所指定):值 意义
allocMemDF 打开诊断内存分配器(默认)。
delayFreeMemDF 在调用 delete 或 free 时延迟释放内存,直到程序退出。这将使您的程序分配可能的最大内存量。
checkAlwaysMemDF 每次分配或释放内存时均调用 AfxCheckMemory。

可以通过执行逻辑 OR 操作来组合使用这些值,如下所示:

AfxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;

检测内存泄漏

创建一个 CMemoryState 对象,并调用 Checkpoint 成员函数。这将创建第一个内存快照。
在程序执行了其内存分配和释放操作以后,创建另一个 CMemoryState 对象,并为该对象调用 Checkpoint。这将得到内存使用的第二个快照。
创建第三个 CMemoryState 对象,并调用其 Difference 成员函数,将前两个 CMemoryState 对象作为参数提供。如果这两个内存状态之间有差异,则 Difference 函数返回非零值。这指示有些内存块尚未被释放。
本示例显示相应的代码:

// Declare the variables needed
#ifdef _DEBUG
  CMemoryState oldMemState, newMemState, diffMemState;
  oldMemState.Checkpoint();
#endif

  // Do your memory allocations and deallocations.
  CString s = "This is a frame variable";
  // The next object is a heap object.
  CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );

#ifdef _DEBUG
  newMemState.Checkpoint();
  if( diffMemState.Difference( oldMemState, newMemState ) )
  {
    TRACE( "Memory leaked! " );
  }
#endif
请注意内存检查语句由 #ifdef _DEBUG / #endif 块括起来,以便它们只在程序的 Win32“Debug”版本中被编译。

既然知道了存在内存泄漏,便可以使用另一个成员函数 CMemoryState::DumpStatistics 来查看内存统计,它将帮助您定位内存泄漏。

Visual Studio  

查看内存统计
CMemoryState::Difference 函数查看两个内存状态对象,并检测起始状态和结束状态之间未从堆释放的任何对象。在您拍内存快照并用 CMemoryState::Difference 比较它们之后,可以调用 CMemoryState::DumpStatistics 获取有关未释放的对象的信息。

请看下面的示例:

if( diffMemState.Difference( oldMemState, newMemState ) )
{
  TRACE( "Memory leaked! " );
  diffMemState.DumpStatistics();
}
从该示例得出的转储示例如下所示:

0 bytes in 0 Free Blocks
22 bytes in 1 Object Blocks
45 bytes in 4 Non-Object Blocks
Largest number used: 67 bytes
Total allocations: 67 bytes
可用块是 afxMemDF 设置为 delayFreeMemDF 时释放被延迟的块。有关更多信息,请参见内存诊断。

第二行中显示的普通对象块仍在堆中保持分配状态。

非对象块包括通过 new 分配的数组和结构。在此例中,堆中分配了四个非对象块,但均未释放。

Largest number used 给出程序在任意时候所使用的最大内存。

Total allocations 给出程序所使用的内存总量。


Visual Studio  

对象转储
在 MFC 程序中,可以使用 DumpAllObjectsSince 转储有关堆中尚未释放的所有对象的说明。DumpAllObjectsSince 转储自上个 CMemoryState::Checkpoint 以来分配的所有对象。如果未发生 Checkpoint 调用,则 DumpAllObjectsSince 将转储当前在内存中的所有对象和非对象。

注意   必须先启用诊断跟踪,然后才能使用 MFC 对象转储。
注意   程序退出时 MFC 将自动转储所有泄漏的对象,因此不必创建代码在该点转储对象。
以下代码通过比较两个内存状态来测试内存泄漏,并在检测到泄漏时转储所有对象:

if( diffMemState.Difference( oldMemState, newMemState ) )
{
  TRACE( "Memory leaked! " );
  diffMemState.DumpAllObjectsSince();
}
转储的内容如下所示:

Dumping objects ->

{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4

Last Name: Smith
First Name: Alan
Phone #: 581-0215

{1} strcore.cpp(80) : non-object block at $00A7516E, 25 bytes long
大多数行开始处的大括号中的数字指定对象的分配顺序。最近分配的对象具有最高编号,并显示在转储的顶部。有关对该示例的更详细分析,请参见解释对象转储。

若要从对象转储获取最大信息量,可以重写 CObject 派生的任何对象的 Dump 成员函数,以自定义对象转储。

通过将全局变量 _afxBreakAlloc 设置为大括号中显示的数字,可以在特定内存分配上设置断点。如果重新运行程序,调试器将在该分配发生时中断执行。然后可以查看调用堆栈,以了解程序是怎样到达该点的。

C 运行时库有一个类似的函数 _CrtSetBreakAlloc,可用于 C 运行时分配。

Visual Studio  

启用内存泄漏检测
检测内存泄漏的主要工具是调试器和 CRT 调试堆函数。若要启用调试堆函数,请在程序中包括以下语句:

#define CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
注意   #include 语句必须采用上文所示顺序。如果更改了顺序,所使用的函数可能无法正确工作。
通过包括 crtdbg.h,将 malloc 和 free 函数映射到其“Debug”版本 _malloc_dbg 和 _free_dbg,这些函数将跟踪内存分配和释放。此映射只在调试版本(在其中定义了 _DEBUG)中发生。发布版本使用普通的 malloc 和 free 函数。

#define 语句将 CRT 堆函数的基版本映射到对应的“Debug”版本。并非绝对需要该语句,但如果没有该语句,内存泄漏转储包含的有用信息将较少。

在添加了上面所示语句之后,可以通过在程序中包括以下语句来转储内存泄漏信息:

_CrtDumpMemoryLeaks();
当在调试器下运行程序时,_CrtDumpMemoryLeaks 将在“输出”窗口中显示内存泄漏信息。内存泄漏信息如下所示:

Detected memory leaks!
Dumping objects ->
C:PROGRAM FILESVISUAL STUDIOMyProjectsleaktestleaktest.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.
如果不使用 #define _CRTDBG_MAP_ALLOC 语句,内存泄漏转储如下所示:

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 时,所显示的会是:

内存分配编号(在大括号内)。
块类型(普通、客户端或 CRT)。
十六进制形式的内存位置。
以字节为单位的块大小。
前 16 字节的内容(亦为十六进制)。
定义了 _CRTDBG_MAP_ALLOC 时,还会显示在其中分配泄漏的内存的文件。文件名后括号中的数字(本示例中为 20)是该文件内的行号。

转到源文件中分配内存的行

在“输出”窗口中双击包含文件名和行号的行。
- 或 -

在“输出”窗口中选择包含文件名和行号的行,然后按 F4 键。
_CrtSetDbgFlag
如果程序总在同一位置退出,则调用 _CrtDumpMemoryLeaks 足够方便,但如果程序可以从多个位置退出该怎么办呢?不要在每个可能的出口放置一个对 _CrtDumpMemoryLeaks 的调用,可以在程序开始包括以下调用:

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
该语句在程序退出时自动调用 _CrtDumpMemoryLeaks。必须同时设置 _CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF 两个位域,如上所示。

设置 CRT 报告模式
默认情况下,_CrtDumpMemoryLeaks 将内存泄漏信息转储到“输出”窗口的“调试”窗格,如上所述。可以使用 _CrtSetReportMode 重置该设置,以转储到另一位置。如果使用库,它可以将输出重置到另一位置。在此情况下,可以使用以下语句将输出位置设置回“输出”窗口:

_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );
有关使用 _CrtSetReportMode 将输出发送到其他位置的信息,请参见 _CrtSetReportMode。
比较内存状态
定位内存泄漏的另一种技术涉及在关键点对应用程序的内存状态拍快照。CRT 库提供一种结构类型 _CrtMemState,您可用它存储内存状态的快照:

_CrtMemState s1, s2, s3;
若要在给定点对内存状态拍快照,请向 _CrtMemCheckpoint 函数传递 _CrtMemState 结构。该函数用当前内存状态的快照填充此结构:

_CrtMemCheckpoint( &s1 );
通过向 _CrtMemDumpStatistics 函数传递 _CrtMemState 结构,可以在任意点转储该结构的内容:

_CrtMemDumpStatistics( &s1 );
该函数输出如下样式的转储的内存分配信息:

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 比较这两个状态:

_CrtMemCheckpoint( &s1 );
// memory allocations take place here
_CrtMemCheckpoint( &s2 );

if ( _CrtMemDifference( &s3, &s1, &s2) )
  _CrtMemDumpStatistics( &s3 );
顾名思义,_CrtMemDifference 比较两个内存状态(前两个参数),生成为这两个状态之间差异的结果(第三个参数)。在程序的开始和结尾放置 _CrtMemCheckpoint 调用,并使用 _CrtMemDifference 比较结果,是检查内存泄漏的另一种方法。如果检测到泄漏,则可以使用 _CrtMemCheckpoint 调用通过二进制搜索技术来划分程序和定位泄漏。

 

你可能感兴趣的:(框架,object,delete,mfc,leak,variables)