内存的管理机制是写代码过程中一个比较重要的功能模块,因为内存泄露是很难调试出来的,会给软件埋下定时炸弹。在C++
环境下有相关的内存检测的方式,这里简单的罗列一下,避免以后自己会忘记,基本上是参考网络上面一篇文章:
在VC下面有这样的一个头文件crtdbg.h,专门用于内存的管理。
我们会用到里面很重要的几个函数。其中最重要的是 _CrtDumpMemoryLeaks();自己看MSDN里的帮助吧。使用这个函数,需要包含头文件crtdbg.h
该函数只在Debug版本才有用,当在调试器下运行程序时,_CrtDumpMemoryLeaks 将在“Output(输出)”窗口中显示内存泄漏信息.写段代码试验一下吧,如下:
#include "stdafx.h"
#include <crtdbg.h>
int _tmain(int argc, _TCHAR* argv[])
{
int* p = new int();
_CrtDumpMemoryLeaks();
return 0;
}
注意这个函数只有debug条件下才会有作用,并且答应的信息输出在vc下面的output窗口中。
Detected memory leaks!
Dumping objects ->
{104} normal block at 0x00697870, 4 bytes long.
Data: < > 00 00 00 00
Object dump complete.
The program '[4688] MemDebug.exe: Native' has exited with code 0 (0x0).
但是这个函数还是很郁闷,我们不知道内存泄露在什么地方,于是我们有了内存泄露的第二个版本。
在引入这个头文件之前,假如这样的宏
#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
#include <crtdbg.h>
int _tmain(int argc, _TCHAR* argv[])
{
int* p = new int();
_CrtDumpMemoryLeaks();
return 0;
}
我们会发现现在的打印信息为
Detected memory leaks!
Dumping objects ->
i:/vc_test/memdebug/memdebug/memdebug.cpp(21) : {104} client block at 0x003B7870, subtype 0, 4 bytes long.
Data: < > 00 00 00 00
Object dump complete.
The program '[3064] MemDebug.exe: Native' has exited with code 0 (0x0).
这样就包含了泄露内存最开始分配的时候的地址。
该程序定义了几个宏,通过宏将Debug版本下的new给替换了,新的new记录下了调用new时的文件名和代码行.
但是这个还不是最终版本,我们可以这样的代码来测试:
int _tmain(int argc, _TCHAR* argv[])
{
int* p = new int();
_CrtDumpMemoryLeaks();
delete p;
return 0;
}
发现我们释放掉的时候,还是有提示说内存错误,这个就有问题了。
运行后可以发现我们删除了指针,但是它仍然报内存泄露。所以可以想象,每调用一次new,程序内部都会将该调用记录
下来,类似于有个数组记录,假如delete了,那么就将其从数组中删除,而_CrtDumpMemoryLeaks()就是把这个数组
当前的状态打印出来。所以除了在必要的时候Dump出内存信息外,最重要的就是在程序退出的时候需要掉用一次_CrtDumpMemoryLeaks();
假如程序有不止一个出口,那么我们就需要在多个地方都调用该函数。
这个就是说,我们把debug模式下的new改变了一下,这个时候每一次new都会忘某个调试分配的表中添加一条信息,如果
delete,这个信息就会删掉,我们只能在程序最终退出的地方加上这样的一个函数CrtDumpMemoryLeaks表示需要打印
内存泄露。程序改成这样就会没有:
int _tmain(int argc, _TCHAR* argv[])
{
int* p = new int();
delete p;
_CrtDumpMemoryLeaks();
return 0;
}
根据这个原理我们可以推断出下面的程序也会出现内存泄露的错误:
class Test
{
public:
Test() { _p = new int(); }
~Test() { delete _p; }
int* _p;
};
int _tmain(int argc, _TCHAR* argv[])
{
int* p = new int();
delete p;
Test t;
_CrtDumpMemoryLeaks();
return 0;
}
造成这个的原因就是我们没有保证这个函数是在程序最后调用的,因为Test t是一个局部类,在程序退出的时候,程序会自动
的调用析构,这个调用析构在CrtDumpMemoryLeaks之前,所以就会有这样的问题,如何来改进呢?如何保证我们的这个CrtDumpMemoryLeaks的调用一定在程序的最后,这个在我们自己的代码中是无法做到的,因为C++的析构是编译器的
特性,析构的调用是编译器自己调用的。
但是我们有这样的一个宏,让编译器知道程序退出的时候调用我们这个函数:
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
代码如下:
class Test
{
public:
Test() { _p = new int(); }
~Test() { delete _p; }
int* _p;
};
int _tmain(int argc, _TCHAR* argv[])
{
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
int* p = new int();
delete p;
Test t;
return 0;
}
但是我觉得光这样还不够,因为我们只是在Output窗口中输出信息,对开发人员的提醒还不明显,经常会被遗漏,而且很
多人就算发现了内存泄露,但是不好 修复,不会严重影响到程序外在表现,都不会修复。怎么样能让开发人员主动的修
复内存泄露的问题呢?记得曾经和人配合写程序,我的函数参数有要求,不能为 空,但是别人老是传空值,没办法了,
只好在函数开始验证函数参数,给他assert住,这样程序运行时老是不停的弹出assert,调试程序那个烦压,最 后其他程
序员烦了,就把这个问题给改好了,输入参数就正确了。所以我觉得咱要让程序员主动去做一件事,首先要让他觉得做这个
事是能减轻自己负担,让自己工 作轻松的。呵呵,那咱们也这样,当程序退出时,检测到内存泄露就让程序提示出来。
代码如下:
#include "stdafx.h"
#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
#include <crtdbg.h>
#include <assert.h>
class Test
{
public:
Test() { _p = new int(); }
~Test() { }
int* _p;
};
void Exit()
{
int i = _CrtDumpMemoryLeaks();
assert( i == 0);
}
int _tmain(int argc, _TCHAR* argv[])
{
atexit(Exit);
int* p = new int();
delete p;
Test t;
return 0;
}
运行起来果然发现,Test析构的时候没有释放掉内存,现在终于知道xp下面经常弹出来的那个终止忽略调试对话框是如何
出来的。