内存泄露检测简说
我们首先从一段代码说起。
#define _CRTDBG_MAP_ALLOC
#include
#include
class test {
public:
test () {
lpBuffer = new char[0x1000];
};
\~test () {
delete lpBuffer;
};
void *operator new (size_t s) {
return malloc (s);
};
void operator delete (void *pvMem) {
if (pvMem != NULL)
free (pvMem);
};
void *operator new[] (size_t s) {
return malloc (s);
};
void operator delete[] (void *pvMem) {
if (pvMem != NULL)
free (pvMem);
};
char *lpBuffer;
};
test & tt ()
{
static test t;
return t;
}
//test t;
void process ()
{
test tf;
// _CrtDumpMemoryLeaks ();
}
int _tmain (int argc, _TCHAR * argv[])
{
// test tf;
test* tp=new test();
_CrtSetReportMode (_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile (_CRT_WARN, _CRTDBG_FILE_STDERR);
_CrtSetDbgFlag (_CRTDBG_ALLOC_MEM_DF |
_CRTDBG_LEAK_CHECK_DF);
process ();
// printf("hello, world.n", t.lpBuffer);
printf ("hello, world.n", tt ().lpBuffer);
// _CrtDumpMemoryLeaks ();
// printf("hello, world.n", t.lpBuffer);
return 0;
}
以上代码,大家可以分别注释不同位置,来查看不同效果。不过我们还是从最基础的如何检测开始说起吧。
首先在头中引入。
#define _CRTDBG_MAP_ALLOC
#include
#include
在引入后,程序结束的时候调用_CrtDumpMemoryLeaks ();就可以打印出来内存泄露的情况。不过有几个问题,首先,打印出来的位置是在调试中。也就是说,如果不使用调试信息查看工具,是看不到信息的。这个问题不难解决。我们可以在程序开始的时候设定。
_CrtSetReportMode (_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile (_CRT_WARN, _CRTDBG_FILE_STDERR);
将出错信息打印到Console输出中。当然如果程序是BCB或者是MFC,也大可打印到文件中。
其次,打印出来的出错信息可能是这样的。
- Detected memory leaks!
- Dumping objects ->
- c:program filesmicrosoft visual studio .net 2003vc7includecrtdbg.h(692)
- {44} normal block at 0x00342A28, 4096 bytes long. Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete.
很好用没错,不过为什么文件上面写的是crtdbg.h呢?其实这个是crtdbg.h所插入的operator new,用来创建内存区域的。malloc在其中被调用,因此所有用new的地方出现的泄露都会是这个位置。
解决这个问题稍微有些烦琐,需要持续的将operator new重载出来。
void *operator new (size_t s) {
return malloc (s);
};
void operator delete (void *pvMem) {
if (pvMem != NULL)
free (pvMem);
};
void *operator new[] (size_t s) {
return malloc (s);
};
void operator delete[] (void *pvMem) {
if (pvMem != NULL)
free (pvMem);
};
当然,理论上我们可以使用多继承,简单的使得某个类具备这个特性。不过遗憾的是多继承往往容易出错,不如手写安全。其次是如果对象并不构建在堆中(更准确的表述是没有使用malloc来分配空间),那么就不会调用operator
new。当然,这时候多数情况下都是栈对象和临时对象,会被系统自动析构。这样我们一般可以得到更好的输出。
- Detected memory leaks!
- Dumping objects ->
- c:program filesmicrosoft visual studio .net 2003vc7includecrtdbg.h(692)
- {44} normal block at 0x00342A28, 4096 bytes long. Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD d:my documentsvisual studio projectstest_memleaktest_memleak.cpp(18) : {43}normal block at 0x00340FD0, 4 bytes long. Data: <(*4 > 28 2A 34 00 Object dump complete.
以上有两个泄露,一个是char test::*lpBuffer;的,另外一个是所分配的空间的。test_memleak.cpp已经被指出了,相信再查不出就是个人水平问题了。(提示,不行可以对operator new下断点)
最后一个,就是退出的时间。我们在调用_CrtDumpMemoryLeaks ();的时候,会打印出当前没有释放的内存占用。如果在退出时打印,就可以得到泄露的内存。这是抓内存泄露的基础。但是有的时候有多个退出点,这个时候,我们就必须使用如下办法。
_CrtSetDbgFlag (_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
这样会在退出的时候自动调用_CrtDumpMemoryLeaks ();。当然,这个办法也不是万能的。如果程序中出现exit(-1);,那么会打印内存泄露然后退出。如果出现ExitProcess(-1);那就啥都没了。不过这个时候打出来的东西肯定也是废物了。中途退出,没有释放掉东西是正常的很的事情。
另外两者还有一点区别,当使用_CrtDumpMemoryLeaks ();的时候,我们认为是程序结束了,实际上并没有。在调用exit(-1);的时候其实执行了C/C++的退出步骤,这个在Crt里面有源码的。大致来说有以下几个区别。
一,main函数中构造对象的析构函数未调用。
二,全局变量未析构。
三,局部静态变量未析构。
很多时候我们在最后打印出来发现有泄露,找半天又找不出。很可能就是以上问题,尤其是第三个问题。