Bootstrap

C++内存泄漏的检测

内存泄漏一直是程序员们头痛的问题。尤其是程序大了。不知道在什么地方悄悄出现了泄漏。程序的逻辑还是对的,暂时没有表现出什么异常,但内存不停的消耗,越来越大,系统越来越慢,最终崩溃。过去调试内存泄漏花费了程序员无数的心血。

C++中,这个问题有了答案。就是重载new。在基础类里面重载的new不仅可以为基础类对象分配内存,还可以为派生类对象分配内存。无论派生类是单重继承的,还是多重继承的。这个特性可以用来追踪内存分配问题。首先,事先写好一个重载了new的base类。当怀疑某个class存在内存泄漏时,让它临时继承这个base类,就可以全程追踪这个class所有的内存分配了。

base类大概是这个样子。它把每次分配的指针存到一个set,或者别的什么容器中。这样随时可以掌握分配了多少元素,每个元素在什么位置。如果写程序的人自以为内存已经全部释放,而容器中还有元素,就说明存在了内存泄漏。

class base {
public:
        static set<void*> s;
        void *operator new(size_t sz);
        void *operator new[](size_t sz);
        static void imp_delete(void *p);
        void operator delete(void *p) { imp_delete(p);}
        void operator delete[](void *p) { imp_delete(p);}
        static int count() {return s.size(); }
};
set<void *> base::s;

void* base::operator new(size_t sz)
{
        void *p;
        printf("base:new, alloc %u\n", sz);
        p = (void *) ::new char[sz];
        s.insert(p);
        return p;
}

void* base::operator new[](size_t sz)
{
        void *p;
        printf("base:new[], alloc %u\n", sz);
        p = (void *) ::new char[sz];
        s.insert(p);
        return p;
}

void base::imp_delete(void *p)
{
        set<void*>::iterator find;
        find = s.find(p);
        if (find ==s.end()) {
                printf("can not delete ptr %p\n", p);
                return;
        }
        s.erase(find);
        delete (char*)p;
        printf("ptr %p delete done.\n", p);
}

重载delete除了跟new配合的需要,同时也检测是否有多次删除同一个指针的行为。

假设原来的class是这样的:

class Other {
public:
        Other() {printf("%s\n", __FUNCTION__);}
};

class D: public Other
{
public:
        int d;
        D() {printf("%s\n", __FUNCTION__);}
};

为了追踪class D的内存问题,只需让D简单的继承一下重载过new 的base类,一切追踪都是自动完成。这是个多重继承:

class Dpublic base, public Other
{
public:
        int d;
        D() {printf("%s\n", __FUNCTION__);}
};

完整的代码是这样的,

#include <stdio.h>
#include <new>
#include <set>
using std::set;


class base {
public:
        static set<void*> s;
        void *operator new(size_t sz);
        void *operator new[](size_t sz);
        static void imp_delete(void *p);
        void operator delete(void *p) { imp_delete(p);}
        void operator delete[](void *p) { imp_delete(p);}
        static int count() {return s.size(); }
};
set<void *> base::s;


void* base::operator new(size_t sz)
{
        void *p;
        printf("base:new, alloc %u\n", sz);
        p = (void *) ::new char[sz];
        s.insert(p);
        return p;
}

void* base::operator new[](size_t sz)
{
        void *p;
        printf("base:new[], alloc %u\n", sz);
        p = (void *) ::new char[sz];
        s.insert(p);
        return p;
}

void base::imp_delete(void *p)
{
        set<void*>::iterator find;
        find = s.find(p);
        if (find ==s.end()) {
                printf("can not delete ptr %p\n", p);
                return;
        }
        s.erase(find);
        delete (char*)p;
        printf("ptr %p delete done.\n", p);
}

class Other {
public:
        int o;
        Other() {printf("%s\n", __FUNCTION__);}
};

class D :public base, public Other
{
public:
        int d;
        D() {printf("%s\n", __FUNCTION__);}
};

int main()
{
        base *b;
        b= new D[2];
        printf("base-object mem=%d\n", base::count());
        printf("delete...\n");
        base *b1 = &((D*)b)[1];
        delete b1;
        delete b;
        delete b;
        printf("base-object mem=%d\n", base::count());
        printf ("done\n");
        return 0;
}

这是上面程序的运行结果:

base:new[], alloc 16
Other
D
Other
D
base-object mem=1
delete...
can not delete ptr 0x6b0630
ptr 0x6b0628 delete done.
can not delete ptr 0x6b0628
base-object mem=0
done

可以看到,程序可以随时检查分配的内存。这里也捕捉到了两次错误的删除操作。

当调试完成时,确定class D的内存操作都已正确,只需把D 对重载new的继承删除即可。一切召之即来,挥之即去。

;