Lost Memory

Programming is an art, then be an artist.

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  6 Posts :: 1 Stories :: 0 Comments :: 0 Trackbacks

常用链接

留言簿

我参与的团队

搜索

  •  

积分与排名

  • 积分 - 12045
  • 排名 - 1052

最新评论

阅读排行榜

评论排行榜

在 Windows 下,通常使用 Visual Studio + C/C++ 开发完一个模块或者一个应用后
都要在 DEBUG 模式检测下是否有内存泄露的问题。

内存泄露, 即先前分配的内存使用之后却没有释放。轻微的内存泄漏对程序的性能并没有
明显的影响,一般不易察觉;严重的内存泄漏会对程序的性能有明显的影响,导致程序性
能下降明显,甚至会导致程序 Crash,或者由于内存耗尽,申请不到内存而退出。有时内
存泄漏会导致一些不相关的奇怪的现象出现,从而难以排查。不管内存泄漏严重与否,对
程序的正确性及健壮性都是一个威胁。本文根据网络上搜索到的资料编写而成,具体链接
见文章末尾。另外本文暂时只涉及使用 C Run-Time (CRT) 库对内存进行操作而导致的
内存泄漏问题。

使用 CRT + C/C++ 对内存的申请方式分为两种:1)使用 <stdlib.h> 中的 malloc ,
 calloc;2)使用 C++ 的 new 操作符。
在 Visual Studio 中对 Memory Leak 的检测方法也分为两种:1)通过 <crtdbg.h>
中的 _CrtDumpMemoryLeaks 或者 _CrtSetDbgFlag 输出内存泄漏点;2) 通过
<crtdbg.h> 中的 _CrtMemCheckpoint , _CrtMemDifference ,
 _CrtMemDumpStatistics 来定位内存泄漏点。检测方法 1)一般适用于对一个独立完整
的程序的检测排查,而方法 2)则适用于对复杂程序中的一个的模块的检测排查。

在检测方法 1)中要在程序和开头加上如下代码:

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


NB:对于不使用 malloc , calloc ,只使用 new 对内存进行使用的程序 <stdlib.h> 可不
包含。 [Ref 1] 中说必须保持如上代码的书写顺序,不知是什么原因。(???)

如果程序的退出点只有一个,可以在主程序的入口使用 _CrtSetDbgFlag ,使程序在调试
状态运行结束后,在输出窗口输出内存泄漏点的信息,或者在主程序的出口使用
_CrtDumpMemoryLeaks ,使程序在调试状态运行结束后,在输出窗口输出内存泄漏点的
信息。如果程序有多个退出点,使用 _CrtDumpMemoryLeaks 的话, 需要在每个退出点
对 _CrtDumpMemoryLeaks 进行调用,而使用 _CrtSetDbgFlag 只需在程序的入口点
进行一次调用。相比较而言,如果程序有多个退出点,使用 _CrtSetDbgFlag 会比较方便。

在输出窗口输出的 Memory Leak 的信息格式如下:

Detected memory leaks!
Dumping objects 
->
c:\users\\desktop\workshop\temp.cpp(
46) : {139} normal block at 0x007C96E88 bytes long.
 Data: 
<        > 00 00 00 00 00 00 00 00 
Object dump complete.


此信息指明了内存泄漏点所在的源文件及行号:...\temp.cpp(46),内存的分配号:{139},
内存块的类型:normal block, 内存块的地址:0x007c96e8, 内存泄漏的大小:8 bytes long,
以及此内存块前 8 个字节的内容:Data:<     > 00 00 00 00 00 00 00 00。
上面的 Memory Leak 输出的格式是在使用 malloc , calloc 的情况下输出的。如果使用
new 的话,输出的 Memory Leak 的格式如下:

Detected memory leaks!
Dumping objects 
->
{
139} normal block at 0x004596E88 bytes long.
 Data: 
<        > 00 00 00 00 00 00 00 00 
Object dump complete.


与上一个输出的 Memory Leak 的信息相比,这个输出缺少了内存泄漏点的源文件及行号。
在这种情况下怎么办呢?一般有两种方法:1)定义宏 new ,使得对 new 操作符的调用,
转换到对新定义的宏的调用,在宏定义中加入源文件名与行号,使行在输出的 Memory Leak
的信息中包含有源文件名与行号;2)使用 <crtdbg.h> 中的 _crtBreakAlloc 根据内存
分配号下断点,使得程序运行到内存泄漏点时暂停。

定义宏 new 的代码如下:

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


把些代码放到程序开始的位置,当在 Debug 模式下运行结束后,输出窗口便会输出包含源文
件名与行号的 Memory Leak 的信息了。如果这种方法不可行,那么只能使用第 2) 中方法了,
即使用 _crtBreakAlloc 。
_crtBreakAlloc 实际上是一个 long int 类型的变量,如果在代码中使用的话,需要在程序的
入口处对 _crtBreakAlloc 赋值为待查找的内存泄漏点的内存分配号,比如:

_crtBreakAlloc = 139;

如果在调试的 Watch 窗口中使用的话,需要做一些变化。在 Watch 窗口的 name 列中输入
_crtBreakAlloc 然后在 value 列中输入待查找的内存泄漏点的分配号,比如 139。然后在
DEBUG 模式下继续运行,调试器便会在对分配号为 139 的内存块进行分配时中断,这里查
看代码与堆栈便可定位内存泄漏点的位置了。如果程序中使用的是多线程的 CRT ,在 Watch
窗口中的 name 列中输入的内容要稍作修改,改为 {,,msvcr120d.dll}_crtBreakAlloc。
其中的 120 可根据不同的 Visual Studio 的版本所使用的 CRT 的版本作相应的修改。
上述步骤完全是按照 [Ref 1] 中所描述的方法做的,实际上却行不通,不知为何。(??????)
要想在 Watch 窗口中使得这种方法有效,还需要修改下 name 列中的名字。当修改为
(long*){,,msvcr120d.dll}__crtBreakAlloc 时(两个下划线),再在其 value 列中填入待查
找的内存的分配号便可以了。[Ref 2] 在 Watch 窗口中设置这个值的前提是在程序的入口处,
或者所认为的可能的内存申请语句之前下断点,使程序停在这里后,再按这种方法设置,设置
好后,让程序继续在 DEBUG 模式下运行。

上面所说的是根据程序在 DEUBG 模式下调试运行结束时,output 窗口中输出 Memory Leak
的信息来定位内存泄漏点。下面来说另外一种方法,即使用<crtdbg.h> 中的 _CrtMemCheckpoint ,
 _CrtMemDifference , _CrtMemDumpStatistics 来定位导致内在泄漏的代码块。这种方法同
前面一种方法一样,也需要定义宏 _CRTDBG_MAP_ALLOC,和包含 <crtdbg.h>。

找到可能导致内存泄漏的代码段,用两个 _CrtMemCheckpoint 的调用把可能代码段包起来,
在后一个 _CrtMemCheckpoint 调用之后,调用 _CrtMemDifference ,再使用
_CrtMemDumpStatistics 输出比较结果到 output 窗口。一步步缩小排查的范围,最终找出
导致内存泄漏的元凶。


参考链结:
[Ref 1]: Finding Memory Leaks Using the CRT Library

[Ref 2]: {,,msvcr100d.dll}_crtBreakAllo CXX0017:symbol not found

posted on 2015-03-07 17:11 pamilty 阅读(2736) 评论(0)  编辑 收藏 引用

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理