曲折探索后,这个问题算是水落石出。
我们都被告诫,new和delete,new[]和delete[]要成对出现。如果使用delete 释放new[] 申请的空间会发什么?如下:
T* p = new T [1024];
....//do something
delete p;//会发生什么?
我先告诉你,如果T是一个base type(如,int、double、char),你会发现,程序依然正确运行,不仅如此,内存空间也被正确释放。它的正确运行,让很多人对delete[]和delete的区别的探索就此终止。也难怪在国内的博客中分析delete和delete[]的原理和delete 释放new[]的文章少之又少,很多相关的文章关于这个问题一句带过。
如果代码是这样的:
class A { public: int m; ~A() {} }; int main() { A* ptr = new A[1024]; delete ptr; return 0; }
你可以测试一下这段代码,在VS和g++下都测试一下,看看都出现什么结果。这会增加你日后在程序调试bug的经验(我已经遭遇一次)
要明白这个问题,我们要知道new和new[],delete和delete[]都干什么了些什么。
不用多说new和delete。
1.new封装了C语言中的malloc 函数,申请一块内存空间,如果T是用户自定义的类类型,将使用T的构造函数初始化这块内存空间上的对象。
2.delete封装了C语言中的free 函数,如果T是用户自定义的类类型,首先调用T的析构函数,再调用free释放这个对象占用的内存空间。
new[]会更复杂一些:
T* ptr = new T [1024]; delete[] ptr;
我们注意到,没有传入任何关于ptr指针的信息给delete[]操作符运算符(operator)。最初的C++编译器版本是要求程序员传入关于指针指向空间的大小的参数,而现代的编译器虽然也接受,但多数情况会忽视它[1]!
既然没有传入参数,那编译器一定是通过某种手段获知ptr指向空间的大小。
是的,说某种手段,是因为有多种方法。在C++发展的最初,有一种关联数组的方法:T* ptr = new[s]申请一段空间后,编译器会在内存的某个地方固定的数组中存入ptr和ptr对应的空间大小s,下次在调用delete[]的时候,就会查找这个数组中的ptr对应的s。[2]
这个方法,现代的编译器已经很少见了,因为它很慢。
现代的g++、vc、icpc都使用信息头(additional information head block)的方法来管理。在new[]申请内存空间时,会多申请n个字节,这n个字节就是信息头,位于我们ptr指针向低地址偏移n个字节的地方(block = ptr - n)。每次delete[]被调用时,编译器就会去这个信息头查找申请的内存信息。
所以,调用new[]得到的东西和调用malloc得到的东西并不相同!
现在,我们分析一下
A* ptr = new A [1024];
delete ptr;
这段代码在delete的时候发生了什么:
首先,和delete任何时候一样,编译器会给delete指派A的析构函数,以函数指针的形式,delete会调用这个析构函数,作用于ptr指向的这个对象上,显然ptr之后的1023个对象不会调用析构函数,因为我们使用的是delete而不是delete[]。然后,delete执行free函数,释放申请的空间。
但是!我们new[]中调用malloc得到的指针并不是从ptr开始的,我们的程序找不到malloc分配的ptr指针,就会崩溃![3]
而为什么base type不会有问题呢?程序运行得好好的。是的,都是编译器的原因。编译器检查到,如果是base tyep,new[]操作就会省去前面的信息头,后面发生delete释放new[]的时候,free调用的ptr和malloc调用的ptr也就一致了。当然,这不一定,也有可能某些编译器,在某些时候,即便是base type它也要加上head block呢?!
所以,如果new和delete,new[]和delete[]不配对,发生什么,未定义!
看到这里,相信你已经明白了delete[]为什么要对应new[]了,而delete也不能和delete[]混用的原因。
有什么问题欢迎留言,交流。
[1][2]《深入探索C++对象模型》
[3]http://stackoverflow.com/questions/8940090/is-it-safe-on-linux-to-mix-new-and-delete ”LiKao“ 的回答