内存泄漏一直是程序员们头痛的问题。尤其是程序大了。不知道在什么地方悄悄出现了泄漏。程序的逻辑还是对的,暂时没有表现出什么异常,但内存不停的消耗,越来越大,系统越来越慢,最终崩溃。过去调试内存泄漏花费了程序员无数的心血。
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 D:public 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的继承删除即可。一切召之即来,挥之即去。