Shell's Home

Feb 7, 2007 - 2 minute read - Comments

内存泄露检测简说

我们首先从一段代码说起。

#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,也大可打印到文件中。

其次,打印出来的出错信息可能是这样的。

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.

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。当然,这时候多数情况下都是栈对象和临时对象,会被系统自动析构。这样我们一般可以得到更好的输出。

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.

<(*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函数中构造对象的析构函数未调用。

二,全局变量未析构。

三,局部静态变量未析构。

很多时候我们在最后打印出来发现有泄露,找半天又找不出。很可能就是以上问题,尤其是第三个问题。

Tags: c memory-leak program

CPUID 广州出差记

comments powered by Disqus