刚写完一段代码,由于将很运行在移动设备上, 我决定先测试一下内存的使用量, 结果发现了很严重的内存泄漏, 在前前后后翻看了new 和delete并确认没有漏写的情况下, 泄露依然存在!调试后最终确认了问题是因为union的不当使用造成的, 下面开始还原现场:
1 typedef struct DATA_ 2 { 3 DATA_(int size = 10) 4 { 5 pVoid = new char[nSize]; 6 this->size = size; 7 } 8 virtual ~DATA_() 9 { 10 if (pVoid) 11 { 12 delete pVoid; 13 } 14 } 15 int nSize; 16 void *pVoid; 17 }DATA, *LPDATA; 18 19 struct STRUCT1 20 { 21 STRUCT1(int count) 22 { 23 pVoid = new DATA[count]; 24 this->count = count; 25 } 26 virtual ~STRUCT1() 27 { 28 if (pVoid) 29 { 30 delete pVoid; 31 } 32 } 33 int count; 34 union 35 { 36 void *pVoid; 37 LPDATA pData; 38 } 39 } 40 41 int main(int argc, char* argv[]) 42 { 43 for( int i = 0; i < 1000; i++) 44 { 45 void *p = new STRUCT1( 10 ); 46 delete p; 47 } 48 return 0; 49 }
在上面这段代码中,STRUCT1中包含了若干个DATA结构,DATA结构又申请了默认为10byte大小的内存,并且内存在对象析构的时候会用delete回收。乍一看这个代码貌似不会泄露内存,其实不然,待我分析:
在C++中构造函数与析构函数的调用是由编译器完成的,其中构造函数的调用一般是在对象空间开辟完以后(栈对象或者堆对象都是一样的),将参数压栈,this指针(对象的起始地址)放入eax寄存器(不同的编译器做法可能不同),然后跳到构造函数去执行。而析构函数的调用则是当对象内存被回收的时候被调用(栈对象是当代码执行到变量可见域外之前调用, 堆对象是在delete语句的位置进行调用)。
然而编译器并是那么智能的可以理解coder的意图,析构函数的调用是根据当前delete的指针类型来确定的,而(下面)这段代码却没有提供类型, 这导致了DATA_的析构函数将不会被调用,内存泄漏就在所难免了。
virtual ~STRUCT1() { if (pVoid) { delete pVoid;//问题在这, 应该使用delete pData; } }