本文已经迁移到: http://cpp.winxgui.com/cn:the-fastest-way-to-find-memory-leak

最快速度找到内存泄漏

许式伟
2006年11月某日

内存管理是C++程序员的痛。我的《 内存管理变革》系列就是试图讨论更为有效的内存管理方式,以杜绝(或减少)内存泄漏,减轻C++程序员的负担。由于工作忙的缘故,这个系列目前未完,暂停。
这篇短文我想换个方式,讨论一下如何以最快的速度找到内存泄漏。

确认是否存在内存泄漏

我们知道,MFC程序如果检测到存在内存泄漏,退出程序的时候会在调试窗口提醒内存泄漏。例如:

class  CMyApp :  public  CWinApp
{
public :
   BOOL InitApplication()
   {
        int *  leak  =   new   int [ 10 ];
        return  TRUE;
   }
};

产生的内存泄漏报告大体如下:

Detected memory leaks !
Dumping objects  ->
c:/work/test.cpp( 186 ) : { 52 } normal block at  0x003C4410 ,  40  bytes  long .
 Data:  <                  >  CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.

这挺好。问题是,如果我们不喜欢MFC,那么难道就没有办法?或者自己做?

呵呵,这不需要。其实,MFC也没有自己做。内存泄漏检测的工作是VC++的C运行库做的。也就是说,只要你是VC++程序员,都可以很方便地检测内存泄漏。我们还是给个样例:

#include  < crtdbg.h >

inline  void  EnableMemLeakCheck()
{
   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)  |  _CRTDBG_LEAK_CHECK_DF);
}

void  main()
{
   EnableMemLeakCheck();
    int *  leak  =   new   int [ 10 ];
}

运行(提醒:不要按Ctrl+F5,按F5),你将发现,产生的内存泄漏报告与MFC类似,但有细节不同,如下:

Detected memory leaks !
Dumping objects  ->
{ 52 } normal block at  0x003C4410 ,  40  bytes  long .
 Data:  <                  >  CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.

为什么呢?看下面。

定位内存泄漏由于哪一句话引起的

你已经发现程序存在内存泄漏。现在的问题是,我们要找泄漏的根源。

一般我们首先确定内存泄漏是由于哪一句引起。在MFC中,这一点很容易。你双击内存泄漏报告的文字,或者在Debug窗口中按F4,IDE就帮你定位到申请该内存块的地方。对于上例,也就是这一句:

int* leak = new int[10];

这多多少少对你分析内存泄漏有点帮助。特别地,如果这个new仅对应一条delete(或者你把delete漏写),这将很快可以确认问题的症结。

我们前面已经看到,不使用MFC的时候,生成的内存泄漏报告与MFC不同,而且你立刻发现按F4不灵。那么难道MFC做了什么手脚?

其实不是,我们来模拟下MFC做的事情。看下例:

inline  void  EnableMemLeakCheck()
{
   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)  |  _CRTDBG_LEAK_CHECK_DF);
}

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

void  main()
{
   EnableMemLeakCheck();
    int *  leak  =   new   int [ 10 ];
}

再运行这个样例,你惊喜地发现,现在内存泄漏报告和MFC没有任何分别了。

快速找到内存泄漏

单确定了内存泄漏发生在哪一行,有时候并不足够。特别是同一个new对应有多处释放的情形。在实际的工程中,以下两种情况很典型:

  • 创建对象的地方是一个类工厂(ClassFactory)模式。很多甚至全部类实例由同一个new创建。对于此,定位到了new出对象的所在行基本没有多大帮助。
  • COM对象。我们知道COM对象采用Reference Count维护生命周期。也就是说,对象new的地方只有一个,但是Release的地方很多,你要一个个排除。

那么,有什么好办法,可以迅速定位内存泄漏?

答:有。

在内存泄漏情况复杂的时候,你可以用以下方法定位内存泄漏。这是我个人认为通用的内存泄漏追踪方法中最有效的手段。

我们再回头看看crtdbg生成的内存泄漏报告:

Detected memory leaks !
Dumping objects  ->
c:/work/test.cpp( 186 ) : { 52 } normal block at  0x003C4410 ,  40  bytes  long .
 Data:  <                  >  CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.

除了产生该内存泄漏的内存分配语句所在的文件名、行号为,我们注意到有一个比较陌生的信息:{52}。这个整数值代表了什么意思呢?

其实,它代表了第几次内存分配操作。象这个例子,{52}代表了第52次内存分配操作发生了泄漏。你可能要说,我只new过一次,怎么会是第52次?这很容易理解,其他的内存申请操作在C的初始化过程调用的呗。:)

有没有可能,我们让程序运行到第52次内存分配操作的时候,自动停下来,进入调试状态?所幸,crtdbg确实提供了这样的函数:即 long _CrtSetBreakAlloc(long nAllocID)。我们加上它:

inline  void  EnableMemLeakCheck()
{
   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)  |  _CRTDBG_LEAK_CHECK_DF);
}

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

void  main()
{
   EnableMemLeakCheck();
   _CrtSetBreakAlloc( 52 );
    int *  leak  =   new   int [ 10 ];
}

你发现,程序运行到 int* leak = new int[10]; 一句时,自动停下来进入调试状态。细细体会一下,你可以发现,这种方式你获得的信息远比在程序退出时获得文件名及行号有价值得多。因为报告泄漏文件名及行号,你获得的只是静态的信息,然而_CrtSetBreakAlloc则是把整个现场恢复,你可以通过对函数调用栈分析(我发现很多人不习惯看函数调用栈,如果你属于这种情况,我强烈推荐你去补上这一课,因为它太重要了)以及其他在线调试技巧,来分析产生内存泄漏的原因。通常情况下,这种分析方法可以在5分钟内找到肇事者。

当然,_CrtSetBreakAlloc要求你的程序执行过程是可还原的(多次执行过程的内存分配顺序不会发生变化)。这个假设在多数情况下成立。不过,在多线程的情况下,这一点有时难以保证。

附加说明:

对“内存管理”相关的技术感兴趣?这里可以看到我的所有关于内存管理的文章。

最快速度找到内存泄漏相关推荐

  1. [转]最快速度找到内存泄漏

    http://hi.baidu.com/%C0%EE%B6%AB%CF%FE/blog/item/0f1983a170a08989471064aa.html 内存管理是C++程序员的痛.我的<内 ...

  2. 彻底理解内存泄漏,memory leak

    作者 | 码农的荒岛求生 来源 | 码农的荒岛求生 内存申请就好比去停车场找停车位,找到停车位后你就可以把车停在这里. 从这个类比看什么是内存泄漏呢?内存泄漏看上去是停车场的车辆只进不出导致最终找不到 ...

  3. 推荐一个检测 JS 内存泄漏的神器

    作为一名 Web 应用程序开发者,排查和修复 JavaScript 代码的内存泄漏一直是最困扰我的问题之一. 最近,Meta 开源了一款检测 JavaScript 代码内存泄漏的框架:MemLab,我 ...

  4. Pytorch内存泄漏Memory Leak

    文章目录 1. 问题描述 2. 问题排查 3. 参考 1. 问题描述 在运行程序是,通过要进行压力测试,在程序的各项性能稳定时才可以进行上线,其中主要的性能指标包含cup,内存,显存,这里遇到的问题就 ...

  5. 解决Solaris应用程序开发内存泄漏问题

    作者: 李凌云,张一峰(laoeyu) 内存泄漏是应用软件开发过程中经常会遇到的问题,应用长期内存泄漏会占用大量操作系统内存资源,直接导致应用程序运行不稳定,严重时甚至还会影响到操作系统的正常运行.为 ...

  6. 初步判断内存泄漏方法

    有时候,内存泄漏不明显,或者怀疑系统有内存泄漏,我们可以通过下面介绍的方法初步确认系统是否存在内存泄漏. 首先在Java命令行中增加-verbose:gc参数, 然后重新启动java进程. 当系统运行 ...

  7. Java 内存泄漏排查,新技能+1

    点击关注公众号,Java干货及时送达 来源 | https://zhenbianshu.github.io/ 前些日子小组内安排值班,轮流看顾我们的服务,主要做一些报警邮件处理.Bug 排查.运营 i ...

  8. 又一次 Java 内存泄漏排查,新技能+1

    点击关注公众号,Java干货及时送达 前些日子小组内安排值班,轮流看顾我们的服务,主要做一些报警邮件处理.Bug 排查.运营 issue 处理的事.工作日还好,无论干什么都要上班的,若是轮到周末,那这 ...

  9. 一次 Java 内存泄漏的排查

    由来 前些日子小组内安排值班,轮流看顾我们的服务,主要做一些报警邮件处理.Bug 排查.运营 issue 处理的事.工作日还好,无论干什么都要上班的,若是轮到周末,那这一天算是毁了. 不知道是公司网络 ...

最新文章

  1. 用了很多年Dubbo,连Dubbo线程池监控都不知道,觉得自己很厉害?
  2. 解决: AttributeError: module 'cv2' has no attribute 'SURF'
  3. android text字体居中显示,Android Canvas的drawText()和文字居中方案
  4. 怎样才能算是一个好的应用程序?
  5. 保障粮食安全-农业大健康-温铁军 谋定落实粮食安全责任
  6. AnotherRedisDesktopManager下载地址
  7. QML TableView表格使用示例
  8. 『数据库』 E-R图(实体联系图)你都不会,你设计什么数据库?
  9. java解码_Java数组已排序解码
  10. mysql炸包_炸裂!MySQL 82 张图带你飞
  11. Kafka分区原理图
  12. 如何使用CSS将文本垂直居中?
  13. DeepMind推出更难的机器阅读理解数据集,要让AI读懂整本书
  14. se105模板,产品详情页去掉多余的review栏目。
  15. Linux服务器使用网络代理
  16. 一次完整的http请求过程是怎样的?
  17. 海量数据处理技巧-转载
  18. 中国天气网城市代码(全,已验证)
  19. RDL 报表 - 横向合并单元格后单元格被撑高
  20. mac android 模拟器 无法运行程序,MAC Android Studio模拟器启动不了

热门文章

  1. plink描述性统计--等位基因频率、缺失值
  2. System Generator从入门到放弃(十)-ADC应用之音频信号采集与输出
  3. React中遇到的Bug——Unhandled Rejection (Error): Maximum update depth exceeded. This can happen when a com
  4. mysql数据模型三要素_E-R模型的三要素为实体、属性、联系-智慧树数据库原理章节答案...
  5. 线阵相机的优势是什么?如何选择线阵相机?
  6. 甲流疫苗“不良反应” adverse reaction
  7. 2、气体灭火系统的设计灭火浓度
  8. 程序的指令和数据为什么分开存放?
  9. IDEA安装 激活 基本使用
  10. 二叉树-求第一条最长路径长度并输出路径