如何检测内存泄漏 —— 重载 new和delete

我曾经参与过一个比较大的项目,在这个项目里面,我们没有一个完全确定的设计文档,所以程序的实现常常变动。虽然我们有一个比较灵活的框架,但 是从程序的角度来讲,它使我们的程序非常的混乱。直到发布的日期临近,我们还没有一个稳定的可以用来做alpha测试的版本。所以我们必须尽快的删除掉无 用的代码,让这个版本足够的稳定。但是,在这个没有足够规范的软件 公司,我们没有时间也没有足够的精力来做边 界测试之类的工作。所以我们只能采用变通的办法。在软件 中最大的问题就是内存泄漏。因为往往出现这 样的情况,我们在一段代码中分配了内存,但是却没有释放它。这造成了很大的问题。我们需要一个简单的解决方案,能够简单的编译进这个项目,在运行的时候, 它能够产生一个没有被释放的内存的列表,用这个列表,我们能够改正程序的错误。这就是我们称之为内存跟踪的方法。首先,我们需要一种代码,能够被加入到源 代码中去,而且这种代码能够被重用。代码重用是一种很重要的特性,能够节省大量的时间和金钱以及程序员的劳动。另外,我们的这种代码必须简单,因为我们当 时已经没有那么多的时间和精力去完全重看一遍所有的代码来重新编写以及改正错误从而使内存跟踪能够起作用。

好在,我们总能够找到解决的办法。首先,我们检查了代码,发现所有的代码都是用new来分配内存,用delete来释放内存。那么,我们能够用一个全程替换,来替换掉所有的new和delete操作符吗?不能。因为代码的规模太大了,那样做除了浪费时间没有别的任何好处。好在我们的源代码是用C++来写成的,所以,这意味着没有必要替换掉所有的new和delete,而只用重载这两个操作符。对了,值用重载这两个操作符,我们就能在分配和释放内存之前做点 什么。这是一个绝对的好消息。我们也知道该如何去做。因为,MFC也是这么做的。我们需要做的是:跟踪所有的内存分配和交互引用以及内存释放。我们的源代 码使用Visual C++写成,当然这种解决方法也可以很轻松的使用在别的C++代码里面。要做的第一件事情是重载new和delete操作符,它们将会在所有的代码中被使用到。我们在stdafx.h中,加入:
#ifdef _DEBUG
inline void * __cdecl operator new( size_t size,  const char *file, int line); 
inline void __cdecl operator delete(void *p);
#endif

operator new 的参数个数是可以任意的, 只需要保证第一个参数为 size_t, 返回类型为 void * 即可, 而且其重载的参数类型也不必包含自定义类型. 更一般的说, operator new 的重载更像是一个函数的重载, 而不是一个操作符的重载.

[★ 用不同的参数重载 operator new]

通过使用不同的参数类型, 可以重载 operator new, 例如 :
void * operator new(size_t size, int x, int y, int z){  ... }

这样,我们就重载了new和delete操作符。我们用$ifdef和#endif来包住这两个重载操作符,这样,这两个操作符就不会在发布版本中出现。 看一看这段代码,会发现,new操作符有三个参数,它们是,分配的内存大小,出现的文件名,和行号。这对于寻找内存泄漏是必需的和重要的。否则,就会需要 很多时间去寻找它们出现的确切地方。加入了这段代码,我们的调用new()的代码仍然是指向只接受一个参数的new操作符,而不是这个接受三个参数的操作 符。另外,我们也不想记录所有的new操作符的语句去包含__FILE__和__LINE__参数。我们需要做的是自动的让所有的接受一个参数的new操 作符调用接受三个参数的new操作符。这一点可以用一点点小的技巧去做,例如下面的这一段宏定义,
#ifdef _DEBUG
#define DEBUG_NEW new(__FILE__, __LINE__)  //对应后2个参数,size由编译器默认提供的
#else
#define DEBUG_NEW new
#endif
#define new DEBUG_NEW
现在我们所有的接受一个参数的new操作符都成为了接受三个参数的new操作符号,__FILE__和__LINE__被预编译器自动的插入到其中了。然 后,就是作实际的跟踪了。我们需要加入一些例程到我们的重载的函数中去,让它们能够完成分配内存和释放内存的工作。这样来 做, 

#ifdef _DEBUG
inline void * __cdecl operator new(size_t size, const char *file, int line)
{
void *ptr = (void *)malloc(size);
AddTrack((DWORD)ptr, size, file, line);
return(ptr);
};
inline void __cdecl operator delete(void *p)
{
RemoveTrack((DWORD)p);
free(p);
};
#endif
另外,还需要用相同的方法来重载new[]和delete[]操作符。这里就省略掉它们了。
最后,我们需要提供一套函数AddTrack()和RemoveTrack()。我用STL来维护存储内存分配记录的连接表。
这两个函数如下:
typedef struct {
DWORD address;
DWORD size;
char file[64];
DWORD line;
} ALLOC_INFO;

typedef list<ALLOC_INFO*> AllocList;
AllocList *allocList;

void AddTrack(DWORD addr, DWORD asize, const char *fname, DWORD lnum)
{
ALLOC_INFO *info;
if(!allocList) {
allocList = new(AllocList);
}
info = new(ALLOC_INFO);
info->address = addr;
strncpy(info->file, fname, 63);
info->line = lnum;
info->size = asize;
allocList->insert(allocList->begin(), info);
};

void RemoveTrack(DWORD addr)
{
AllocList::iterator i;
if(!allocList)
return;
for(i = allocList->begin(); i != allocList->end(); i++)
{
if((*i)->address == addr)
{
allocList->remove((*i));
break;
}
}
};
现在,在我们的程序退出之前,allocList存储了没有被释放的内存分配。为了看到它们是什么和在哪里被分配的,我们需要打印出allocList中 的数据。我使用了Visual C++中的Output窗口来做这件事情。
void DumpUnfreed()
{
AllocList::iterator i;
DWORD totalSize = 0;
char buf[1024];
if(!allocList)
    return;
for(i = allocList->begin(); i != allocList->end(); i++) 

{
sprintf(buf, "%-50s: LINE %d, ADDRESS %d %d unfreed ", (*i)->file, (*i)->line, (*i)->address, (*i)->size);
OutputDebugString(buf);
totalSize += (*i)->size;
}
sprintf(buf, "Total Unfreed: %d bytes ", totalSize);
OutputDebugString(buf);
};
现在我们就有了一个可以重用的代码,用来监测跟踪所有的内存泄漏了。这段代码可以用来加入到所有的项目中去。虽然它不会让你的程序看起来更好,但是起码它 能够帮助你检查错误,让程序更加的稳定。

 

 

---------------------完整代码----------------------------------------------------------

#include <iostream>
#include <list>
using namespace std;

typedef unsigned long DWORD;
typedef struct {
    DWORD address;
    DWORD size;
    char file[64];
    DWORD line;
} ALLOC_INFO;

typedef list<ALLOC_INFO*> AllocList;
AllocList *allocList;

void AddTrack(DWORD addr, DWORD asize, const char *fname, DWORD lnum)
{
    ALLOC_INFO *info;
    if(!allocList)
    {
        allocList = new(AllocList);
    }
    info = new(ALLOC_INFO);
    info->address = addr;
    strncpy(info->file, fname, 63);
    info->line = lnum;
    info->size = asize;
    allocList->insert(allocList->begin(), info);
};

void RemoveTrack(DWORD addr)
{
    AllocList::iterator i;
    if(!allocList)
        return;
    for(i = allocList->begin(); i != allocList->end(); i++)
    {
        if((*i)->address == addr)
        {
            allocList->remove((*i));
            break;
        }
    }
};

#ifdef _DEBUG
inline void* operator new(size_t size,const char *file, int line) //
{
    void *ptr = (void*)malloc(size);
    AddTrack((DWORD)ptr, size, file, line);
    return(ptr);
};
inline void operator delete(void *p)
{
    RemoveTrack((DWORD)p);
    free(p);
};
#endif

#ifdef _DEBUG
    #define DEBUG_NEW new(__FILE__, __LINE__)  //1st parameter:size is not needed, passed by the compiler
#else
    #define DEBUG_NEW new  //if '_DEBUG' is not defined, use normal new()
#endif
//#define new DEBUG_NEW   //define this line to overload all new() operation to DEBUG_NEW

void OutputDebugString(const char* buf)
{
    cout<<buf<<endl;
}

void DumpUnfreed()
{
    AllocList::iterator i;
    DWORD totalSize = 0;
    char buf[1024];
    if(!allocList)
        return;
    for(i = allocList->begin(); i != allocList->end(); i++)
    {
        sprintf(buf, "%-50s: LINE %d, ADDRESS %d %d unfreed ",(*i)->file, (*i)->line, (*i)->address, (*i)->size);
        OutputDebugString(buf);
        totalSize += (*i)->size;
    }
    sprintf(buf, "Total Unfreed: %d bytes ", totalSize);
    OutputDebugString(buf);
};

class Base
{
private:
    int i;  //'int' takes 4 bytes in memory
};

int main()
{
    cout<<"testing own new operator..."<<endl;
    Base* b = DEBUG_NEW Base(); 
    DumpUnfreed();  //display the memory status before delete
    delete b;
    DumpUnfreed();
    cout<<"-------------------------------"<<endl;
    cout<<"testing default new operator..."<<endl;
    Base* b1 = new Base();   //use normal default new, wont have memory track
    DumpUnfreed();
    delete b1;
    DumpUnfreed();
    system("PAUSE");
    return 0;
}

 

--------output-------

testing own new operator...
../src/CPPTests.cpp                               : LINE 99, ADDRESS 1048704 4 unfreed
Total Unfreed: 4 bytes
Total Unfreed: 0 bytes
-------------------------------
testing default new operator...
Total Unfreed: 0 bytes
Total Unfreed: 0 bytes

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值