程序中通常包含着静态存储区和栈内存。静态存储区也就是静态内存,是用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量(全局变量)。栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在定义的程序块运行时才存在,程序退出,栈对象也随之销毁;static对象和全局对象则是在程序结束时销毁。除了静态内存和栈内存,程序还拥有一块内存池,这部分也就是称为堆。在使用堆空间是就需要使用动态内存分配。
当我们在堆上分配的内存使用之后没有手动释放,会造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。下面介绍一些常用的内存泄漏检测方法和工具。
一,MFC宏定义
大家使用visual studio编译器或者VC6.0创建MFC程序的时候,在默认生成的类的cpp文件上方,可以看到使用#ifndef…#define…#endif来定义了一些宏,而这些宏中恰恰就有涉及到内存操作的操作符new。
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
当在debug模式下时,我们分配内存时的new被替换成DEBUG_NEW,而这个DEBUG_NEW不仅要传入内存块的大小,还要传入源文件名和行号。在程序退出时,如果发生内存泄漏,我们可以在编译器的输出界面清楚地看到发生内存泄漏的代码文件以及行号,以便于我们直接定位问题。
在stackoverflow上查找了这种实现方式的原理,也没有答案,但是结合下文要说的第二种方式,我推断MFC应该是自己内部封装实现了_CrtDumpMemoryLeaks()。
二,_CrtDumpMemoryLeaks
C运行库的Debug版本提供了许多检测功能,使得我们更容易的Debug程序,我们会用到里面很重要的几个函数。其中最重要的是 _CrtDumpMemoryLeaks();自己看MSDN里的帮助吧。使用这个函数,需要包含头文件crtdbg.h
该函数只在Debug版本才有用,当在调试器下运行程序时,_CrtDumpMemoryLeaks 将在“Output(输出)”窗口中显示内存泄漏信息。但是这个函数只能输出是否有内存泄漏,并不能定位到哪里出现了内存泄漏,因此我们需要参考mfc的方式来加入文件名称和代码行号。
#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
在我们程序退出前的最后一个地方调用_CrtDumpMemoryLeaks ()即可,例子如下:
int _tmain(int argc, _TCHAR* argv[])
{
int* p = new int();
char* c = new char;
_CrtDumpMemoryLeaks();
return 0;
}
编译器输出界面如下:
双击泄漏的地方即可直接定位代码处进行修改。
注意,debug模式下在堆上分配空间时,编译器为了调试方便会多分配36bytes,使用_CrtDumpMemoryLeaks检测时有时会误报这个36bytes的内存泄漏。
三,VLD
Visual LeakDetector(VLD)是一款用于Visual C++的免费的内存泄露检测工具。它的特点有:
(1)、它是免费开源的,采用LGPL协议;
(2)、它可以得到内存泄露点的调用堆栈,可以获取到所在文件及行号;
(3)、它可以得到泄露内存的完整数据;
(4)、它可以设置内存泄露报告的级别。
VLD的源码可以从https://archive.codeplex.com/?p=vld上下载,找到vld\sourceCode\vld\vld_vs14.sln可以使用vs2015打开.
1,编译vld工程,在src/bin/Win32/Debug-v140下生成了vld.lib和vld_x86.dll和dbghelp.dll。
2,把vld.lib放到工程代码同目录下,把vld_x86.dll和dbghelp.dll放到exe目录下(或dll目录下),同时加上Microsoft.DTfW.DHL.manifest文件(否则会报错”应用程序无法正常启动(0x0150002)“)。
3,把vld.h和vld_def.h这两个头文件加入到工程项目中,在整个程序中都会包含的头文件里加入#include “vld.h”(比如含有预编译头的工程,则直接加到stdafx.h中)。
vld默认只在debug模式下检测内存泄漏,所以我们需要以debug运行程序,测试程序如下:
程序运行结束后,在vs下方的输出界面会有如下输出信息:
可以统计出程序中发生内存泄漏的数目,和泄漏的字节数,并且可以精准定位到代码位置。
四,Tencent tMem Monitor
Tencent tMem Monitor是腾讯推出的一款运行时C/C++内存泄漏检测工具(TTM下载)。TMM认为在进程退出时,堆内存中没有被释放且没有指针指向的无主内存块即为内存泄漏,并进而引入垃圾回收(GC, Garbage Collection)机制,在进程退出时检测出堆内存中所有没有被引用的内存单元,因而内存泄露检测准确率为100%(官方说明)。不过我自己在使用这些工具和方法的过程中,发现TTM的内存泄漏检测准确率是上述所有方法中最高的,特别程序中使用了libxml2库使用不正确导致的内存泄漏也能检测出来。但是TTM也有个小缺点,它并不适用于检测小程序,
TMM工具主要包含两部分,第一部分是客户端的检测界面,客户端部分主要负责监控目标进程中的内存行为并计算内存泄漏。检测时只要将被检测程序添加到监控列表中,然后正常运行被检程序即可,以下为客户端界面:
另一部分是结果的展示与分析。TMM支持本地查看和在线查询(已不支持在线查询)两种方式。本地查看时,提供按泄漏次数或泄漏大小对结果进行排序的功能,并在安装目录的data文件夹中给出详细分析报告。以下为本地查看结果:
看一下20190720这个demo的内存泄漏代码:
102行处代码导致内存泄漏,精准定位。