曲折探索后,這個問題算是水落石出。
我們都被告誡,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“ 的回答