注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

慵懒的乌龟

——若有,且珍惜~

 
 
 

日志

 
 

VS2008 下检测内存泄漏  

2011-10-14 21:04:22|  分类: C/C++ |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
http://blog.csdn.net/bairny/article/details/1613431     Blog 中的一篇文章:“VC++6.0中内存泄漏检测  首先,检测内存泄漏的基本工具是调试器和 CRT 调试堆函数。为了使用调试堆函数,必须在要检测内存泄漏和调试的程序中添加下面的语句:
#define _CRTDBG_MAP_ALLOC 
 #include<stdlib.h>  
 #include<crtdbg.h>  
 #include "debug_new.h"  

  MSDN 如是说:“必须保证上面声明的顺序,如果改变了顺序,可能不能正常工作。”至于这是为什么,我们不得而知。MS 的老大们经常这样故弄玄虚。
  针对非 MFC 程序,再加上周星星的头文件:debug_new.h,当然如果不加这一句,也能检测出内存泄漏,但是你无法确定在哪个源程序文件中发生泄漏。Output 输出只告诉你在 crtsdb.h 中的某个地方有内存泄漏。我测试时 REG_DEBUG_NEW 没有起作用。加不加这个宏都可以检测出发生内存分配泄漏的文件。
  其次,一旦添加了上面的声明,你就可以通过在程序中加入下面的代码来报告内存泄漏信息了:

 _CrtDumpMemoryLeaks();  
  这就这么简单。我在周星星的例子代码中加入这些机关后,在 VC++ 调试会话(按 F5 调试运行) Output 窗口的 Debug 页便看到了预期的内存泄漏 dump。该 dump 形式如下:
Detected memory leaks!  
 Dumping objects ->  
c:/Program Files/.../include/crtdbg.h(552) : {45} normal block at 0x00441BA0, 2 bytes long. Data: <AB> 41 42 c:/Program Files/.../include/crtdbg.h(552) : {44} normal block at 0x00441BD0, 33 bytes long. Data: < C > 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD  
c:/Program Files/.../include/crtdbg.h(552) : {43} normal block at 0x00441C20, 40 bytes long. Data: < C > E8 01 43 00 16 00 00 00 00 00 00 00 00 00 00 00 Object dump complete.

  下面是我看过 MSDN 资料后,针对“如何使用 CRT 调试功能来检测内存泄漏?”的问题进行了一番编译和整理,希望对大家有用。如果你的英文很棒,那就不用往下看了,建议直接去读 MSDN 库中的技术原文。
  C/C++ 编程语言的最强大功能之一便是其动态分配和释放内存,但是中国有句古话:“最大的长处也可能成为最大的弱点”,那么 C/C++ 应用程序正好印证了这句话。在 C/C++ 应用程序开发过程中,动态分配的内存处理不当是最常见的问题。其中,最难捉摸也最难检测的错误之一就是内存泄漏,即未能正确释放以前分配的内存的错误。偶尔发生的少量内存泄漏可能不会引起我们的注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各种各样的征兆:从性能不良(并且逐渐降低)到内存完全耗尽。更糟的是,泄漏的程序可能会用掉太多内存,导致另外一个程序垮掉,而使用户无从查找问题的真正根源。此外,即使无害的内存泄漏也可能殃及池鱼。
  幸运的是,Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法。下面请和我一起分享收获——如何使用 CRT 调试功能来检测内存泄漏?

  1. 如何启用内存泄漏检测机制?
  2. 解释内存块类型
  3. 如何在内存分配序号处设置断点?
  4. 如何比较内存状态?
  5. 结论

如何启用内存泄漏检测机制

  VC++ IDE 的默认状态是没有启用内存泄漏检测机制的,也就是说即使某段代码有内存泄漏,调试会话的 Output 窗口的 Debug 页不会输出有关内存泄漏信息。你必须设定两个最基本的机关来启用内存泄漏检测机制。

一是使用调试堆函数:

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

注意:#include 语句的顺序。如果更改此顺序,所使用的函数可能无法正确工作。

  通过包含 crtdbg.h 头文件,可以将 malloc 和 free 函数映射到其“调试”版本 _malloc_dbg 和 _free_dbg,这些函数会跟踪内存分配和释放。此映射只在调试(Debug)版本(也就是要定义 _DEBUG)中有效。发行版本(Release)使用普通的 malloc 和 free 函数。
  #define 语句将 CRT 堆函数的基础版本映射到对应的“调试”版本。该语句不是必须的,但如果没有该语句,那么有关内存泄漏的信息会不全。

二是在需要检测内存泄漏的地方添加下面这条语句来输出内存泄漏信息:

_CrtDumpMemoryLeaks(); 
  当在调试器下运行程序时,_CrtDumpMemoryLeaks 将在 Output 窗口的 Debug 页中显示内存泄漏信息。比如:
Detected memory leaks! 
Dumping objects ->  
C:/Temp/memleak/memleak.cpp(15) : {45} normal block at 0x00441BA0, 2 bytes long. Data: <AB> 41 42   
c:/program files/microsoft visual studio/vc98/include/crtdbg.h(552) : {44} normal block at 0x00441BD0, 33 bytes long. Data: < C > 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD   
c:/program files/microsoft visual studio/vc98/include/crtdbg.h(552) : {43} normal block at 0x00441C20, 40 bytes long. Data: < C > 08 02 43 00 16 00 00 00 00 00 00 00 00 00 00 00 Object dump complete. 

如果不使用 #define _CRTDBG_MAP_ALLOC 语句,内存泄漏的输出是这样的:

Detected memory leaks!  
Dumping objects ->  
{45} normal block at 0x00441BA0, 2 bytes long. Data: <AB> 41 42   
{44} normal block at 0x00441BD0, 33 bytes long. Data: < C > 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD   
{43} normal block at 0x00441C20, 40 bytes long. Data: < C > C0 01 43 00 16 00 00 00 00 00 00 00 00 00 00 00 Object dump complete. 
  根据这段输出信息,你无法知道在哪个源程序文件里发生了内存泄漏。下面我们来研究一下输出信息的格式。第一行和第二行没有什么可说的,从第三行开始:

 

xx}:花括弧内的数字是内存分配序号,本文例子中是 {45},{44},{43};  
block:内存块的类型,常用的有三种:normal(普通)、client(客户端)或 CRT(运行时);本文例子中是:normal block;   
用十六进制格式表示的内存位置,如:at 0x00441BA0 等;  
以字节为单位表示的内存块的大小,如:32 bytes long;   
前 16 字节的内容(也是用十六进制格式表示),如:Data: <AB> 41 42 等; 

  仔细观察不难发现,如果定义了 _CRTDBG_MAP_ALLOC ,那么在内存分配序号前面还会显示在其中分配泄漏内存的文件名,以及文件名后括号中的数字表示发生泄漏的代码行号,比如:

C:/Temp/memleak/memleak.cpp(15)  
  双击 Output 窗口中此文件名所在的输出行,便可跳到源程序文件分配该内存的代码行(也可以选中该行,然后按 F4,效果一样),这样一来我们就很容易定位内存泄漏是在哪里发生的了,因此,_CRTDBG_MAP_ALLOC 的作用显而易见。

使用 _CrtSetDbgFlag

  如果程序只有一个出口,那么调用 _CrtDumpMemoryLeaks 的位置是很容易选择的。但是,如果程序可能会在多个地方退出该怎么办呢?在每一个可能的出口处调用 _CrtDumpMemoryLeaks 肯定是不可取的,那么这时可以在程序开始处包含下面的调用:
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); 

  这条语句无论程序在什么地方退出都会自动调用 _CrtDumpMemoryLeaks。注意:这里必须同时设置两个位域标志:_CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF。

设置 CRT 报告模式

  默认情况下,_CrtDumpMemoryLeaks 将内存泄漏信息 dump 到 Output 窗口的 Debug 页,如果你想将这个输出定向到别的地方,可以使用 _CrtSetReportMode 进行重置。如果你使用某个库,它可能将输出定向到另一位置。此时,只要使用以下语句将输出位置设回 Output 窗口即可:

_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG ); 

有关使用 _CrtSetReportMode 的详细信息,请参考 MSDN 库关于 _CrtSetReportMode 的描述。

解释内存块类型

  前面已经说过,内存泄漏报告中把每一块泄漏的内存分为 normal(普通块)、client(客户端块)和 CRT 块。事实上,需要留心和注意的也就是 normal 和 client,即普通块和客户端块。

  • normal block(普通块):这是由你的程序分配的内存
  • client block(客户块):这是一种特殊类型的内存块,专门用于 MFC 程序中需要析构函数的对象。MFC new 操作符视具体情况既可以为所创建的对象建立普通块,也可以为之建立客户块
  • CRT block(CRT 块):是由 C RunTime Library 供自己使用而分配的内存块。由 CRT 库自己来管理这些内存的分配与释放,我们一般不会在内存泄漏报告中发现 CRT 内存泄漏,除非程序发生了严重的错误(例如 CRT 库崩溃)。

除了上述的类型外,还有下面这两种类型的内存块,它们不会出现在内存泄漏报告中:

  • free block(空闲块):已经被释放(free)的内存块。
  • Ignore block(忽略块):这是程序员显式声明过不要在内存泄漏报告中出现的内存块。

如何在内存分配序号处设置断点?

  在内存泄漏报告中的文件名和行号可告诉分配泄漏的内存的代码位置,但仅仅依赖这些信息来了解完整的泄漏原因是不够的。因为一个程序在运行时,一段分配内存的代码可能会被调用很多次,只要有一次调用后没有释放内存就会导致内存泄漏。为了确定是哪些内存没有被释放,不仅要知道泄漏的内存是在哪里分配的,还要知道泄漏产生的条件。这时内存分配序号就显得特别有用——这个序号就是文件名和行号之后的花括弧里的那个数字。
  例如,在本文例子代码的输出信息中,“45”是内存分配序号,意思是泄漏的内存是你程序中分配的第四十五个内存块:

Detected memory leaks!  
Dumping objects ->  
C:/Temp/memleak/memleak.cpp(15) : {45} normal block at 0x00441BA0, 2 bytes long. Data: <AB> 41 42 ...... Object dump complete. 

  CRT 库对程序运行期间分配的所有内存块进行计数,包括由 CRT 库自己分配的内存和其它库(如 MFC)分配的内存。因此,分配序号为 N 的对象即为程序中分配的第 N 个对象,但不一定是代码分配的第 N 个对象。(大多数情况下并非如此。)
  这样的话,你便可以利用分配序号在分配内存的位置设置一个断点。方法是在程序起始附近设置一个位置断点。当程序在该点中断时,可以从 QuickWatch(快速监视)对话框或 Watch(监视)窗口设置一个内存分配断点:

  例如,在 Watch 窗口中,在 Name 栏键入下面的表达式:

_crtBreakAlloc 

如果要使用 CRT 库的多线程 DLL 版本(/MD 选项),那么必须包含上下文操作符,像这样:

{,,msvcrtd.dll}_crtBreakAlloc 

  现在按下回车键,调试器将计算该值并把结果放入 Value 栏。如果没有在内存分配点设置任何断点,该值将为 –1。
  用你想要在其位置中断的内存分配的分配序号替换 Value 栏中的值。例如输入 45。这样就会在分配序号为 45 的地方中断。 
  在所感兴趣的内存分配处设置断点后,可以继续调试。这时,运行程序时一定要小心,要保证内存块分配的顺序不会改变。当程序在指定的内存分配处中断时,可以查看 Call Stack(调用堆栈)窗口和其它调试器信息以确定分配内存时的情况。如果必要,可以从该点继续执行程序,以查看对象发生了什么情况,或许可以确定未正确释放对象的原因。
  尽管通常在调试器中设置内存分配断点更方便,但如果愿意,也可在代码中设置这些断点。为了在代码中设置一个内存分配断点,可以增加这样一行(对于第四十五个内存分配):

_crtBreakAlloc = 45; 

你还可以使用有相同效果的 _CrtSetBreakAlloc 函数:

_CrtSetBreakAlloc(45); 

如何比较内存状态?

  定位内存泄漏的另一个方法就是在关键点获取应用程序内存状态的快照。CRT 库提供了一个结构类型 _CrtMemState。你可以用它来存储内存状态的快照:

_CrtMemState s1, s2, s3; 

  若要获取给定点的内存状态快照,可以向 _CrtMemCheckpoint 函数传递一个 _CrtMemState 结构。该函数用当前内存状态的快照填充此结构:

_CrtMemCheckpoint( &s1 ); 

  通过向 _CrtMemDumpStatistics 函数传递 _CrtMemState 结构,可以在任意地方 dump 该结构的内容:

_CrtMemDumpStatistics( &s1 ); 

该函数输出如下格式的 dump 内存分配信息:

0 bytes in 0 Free Blocks.  
75 bytes in 3 Normal Blocks.  
5037 bytes in 41 CRT Blocks.  
0 bytes in 0 Ignore Blocks.  
0 bytes in 0 Client Blocks. 
 Largest number used: 5308 bytes.  
Total allocations: 7559 bytes. 

  若要确定某段代码中是否发生了内存泄漏,可以通过获取该段代码之前和之后的内存状态快照,然后使用 _CrtMemDifference 比较这两个状态:

_CrtMemCheckpoint( &s1 );// 获取第一个内存状态快照 // 在这里进行内存分配 _CrtMemCheckpoint( &s2 );// 获取第二个内存状态快照 // 比较两个内存快照的差异  
if ( _CrtMemDifference( &s3, &s1, &s2) ) 
    _CrtMemDumpStatistics( &s3 );// dump 差异结果 

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

补充来自的方式:http://st251256589.blog.163.com/blog/static/164876449201110134245966/


VS本身就有内存泄露的检测机制。只需做以下操作即可开启。(同时必须在debug模式 下运行程序并且以 正常流程退出 )
// 在入口函数cpp中添加以下定义和头文件  
#define CRTDBG_MAP_ALLOC  
#include <stdlib.h>  
#include <crtdbg.h>  
// 在入口函数一开始添加以下代码  
_CrtDumpMemoryLeaks();  
    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );  

然后在以 正常流程退出 程序后, 就会在输出窗口显示出内存泄露的信息了。

注意!重点来了。

虽然VS检测出来了哪里有了内存泄露, 但是有可能你得到的有效信息只有两个 (如下)

1. 未释放的内存大小。(绿色部分 )

2. 这块内存是程序中总计第几个被申请的。(蓝色部分 )。

3. 而申请空间所在代码的位置可能不会被显示(删除线部分 )。

 

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.

那么, 如果出现了这种情况, 其实是根本无法知道哪里出了问题。那么这个时候该怎么办呢?

这个时候需要使用这个函数。


//num就是刚刚检测出来的内存泄露的地方大括号内的数字  
_CrtSetBreakAlloc(num);  

这个函数的作用是,在你运行程序的时候,会在第num次申请空间时断点。

所以,你需要重新运行一遍程序 。(只要流程一样,内存申请顺序肯定是一样的,所以会正确的在需要的地方断掉。)

注意,之前提示的内存泄露中大括号内的数值就是指的第几次申请的空间泄露了。

这个时候查看堆栈调用 ,即可知道到底是在哪里申请了内存但是忘记释放掉了。然后将这块内存在合适的时候释放掉就行了。

结论

  尽管 VC ++ 具有一套专门调试 MFC 应用程序的机制,但本文上述讨论的内存分配很简单,没有涉及到 MFC 对象,所以这些内容同样也适用于 MFC 程序。在 MSDN 库中可以找到很多有关 VC++ 调试方面的资料,如果你能善用 MSDN 库,相信用不了多少时间你就有可能成为调试高手。

本人水平不高,谬误在所难免,请大家拍砖,不要客气。

  评论这张
 
阅读(3925)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017