Effective C++ 条款16 成对使用new和delete时要采取相同形式
(参考自 http://www.cnblogs.com/hazir/p/new_and_delete.html)
1. new和delete不是函数,是C++的关键字.
2. new一个对象的实际操作分为两步:1). 调用标准库函数operator new分配内存 2)调用构造函数初始化(如果new的是内置类型则略过此步)
delete一个对象的实际操作也分为两步:1). 调用析构函数析构对象(如果new的是内置类型则略过此步) 2). 调用标准库函数operator delete释放内存
new和delete一个数组与new和delete一个对象步骤类似,只不过调用的是new operator[]和operator delete[]函数
3. 当new一个数组时,主要有以下两种情况:
1). 如果new的数组是含有自定义的析构函数的类类型数组,那么在开辟的数组前实际上还额外开辟了四字节空间用于存储数组大小,以便在delete[]的时候确保为每一个对象调用析构函数(当析构函数承担释放堆内存任务时尤其重要),例如:
#include<iostream> using namespace std; class A{ public: A(){} ~A(){} }; int main(){ A* p = new A[10]; cout << *((reinterpret_cast<int*>(p)-1)) << endl; delete []p; system("pause"); return 0; }
运行结果如下:
因此调用operator delete[]释放内存时实际上传入的地址不是数组首地址,而是首地址-4字节.
如果new一个含有自定义的析构函数的类类型数组,那么必须使用delete[]来释放资源,否则会导致:
① 少调用了析构函数 ②调用operator delete时传递的首地址后移了四个字节
其中第二个后果是致命的,它会造成严重的段错误,必然导致程序崩溃!
2). 如果new的数组是内置类型或不含析构函数的类类型的数组,那么将不需要四字节空间用来存储数组大小,例如:
#include<iostream> using namespace std; class A{ public: A(){} //~A(){} }; int main(){ A* p = new A[10]; cout << *((reinterpret_cast<int*>(p)-1)) << endl; delete []p; system("pause"); return 0; }
运行结果为:
可见,并没有设置四字节用来存储数组大小.
因此,如果new一个内置类型或不含析构函数的类类型的数组,那么用delete来释放也是可以的,完全没有不良后果.
综上:new一个含有自定义析构函数的类类型数组和new一个内置类型数组的差别在于之前保留了四字节空间用于为每一个对象调用析构函数,并导致了传给operator delete[]的指针参数也前移了4个字节,从表现上看,operator new和operator new[],operator delete和operator delete[]并无差别(operator new[]和operator delete[]唯一特殊的地方在于它们接受的地址可能前移四个字节也可能不前移)
4. 由3可以得出:对于内置类型对象和不含自定义析构函数的类类型对象,delete和delete[]可以混用;
对于含有自定义析构函数的类类型对象,delete和delete[]必须与new一个对象和new一个数组完全匹配.
由以上事实可以推出以下结论:
当new一个含有自定义析构函数的类类型对象时,如果用delete[]释放会造成程序崩溃;当new一个内置类型或不含自定义析构函数的类类型对象时用delete[]没有问题.
当new[]一个含有自定义析构函数的类类型数组时,如果用delete释放会造成程序崩溃;当new[]一个内置类型或不含自定义析构函数的类类型数组时用delete没有问题.
当然,以上只是用于理解new和delete内部实现机理,只需记住new/delete,new[]/delete[]配套使用即可!