釋放對象數組:delete與delete[]

來源:互聯網
上載者:User

<<c++ primer>>練習   14.11   中提到: 
Account   *parray=new   Account[100]; 
delete   parray; 
delete   []   parray; 
方括弧的存在會使編譯器擷取數組大小(size)然後解構函式再被依次應用在每個元素上,一共size次。否則,只有一個元素被析構。 
無論哪種情況,分配的全部空間被返還給自由儲存區。 

我的問題是:為什麼無論哪種情況,分配的全部空間被返還給自由儲存區? 
對於delete parray,為什麼不是刪除單個Account元素,而是刪除了100個. 
編譯器怎麼知道parray這個指標實際指向的是數組還是單個元素,即便知道指向的是自由儲存區的數組,這個數組的大小又怎麼知道。 
難道是編譯器輔助行為? 

總結:空間釋放(肯定有個log記錄分配的大小)和調用解構函式(類型識別,不同的編譯器實現不同)採用不同的機制. 

(1)一般在分配時分配器會自動寫一個日誌(一般在分配使用得記憶體之前又一個結構),用於記錄分配的大小,分配內容的sizeof等等。 
直觀得想想,delete和delete[]都是傳入一個void*如果不儲存日誌就無法知道分配時到底是分配了一個還是多個單元. 
所以雖然delete和delete[]不同但是分配器在執行釋放過程中都會讀取這個日誌,從而瞭解到底應該釋放多少記憶體,但是從程式員的角度來說,既然分配了數組,就應該用delete[] 

(2)在VC下用彙編跟過delete[]的執行情況,發現這個 "日誌 "就是一個4位元組長的整數記錄數組元素個數,緊挨在數組第一個元素之前.   
但是有個前提:物件類型(或其基類)有顯式解構函式.換句話說,解構函式是非trivial的.   
否則的話,數組前面是沒有這個日誌的.其實對於解構函式是trivial的情況,delete[]時無需調用其解構函式,因此此時VC把delete[]當做delete同樣處理. 

(3)因為釋放數組空間和為數組調用解構函式是兩個獨立的部分,可以使用不同的機制來實現。 
釋放空間的機制是需要絕對保證的。因此,即使你不寫delete[],它也會將所有空間釋放,其機制可以是前置的長度資訊,也可以不是(如後置的特徵分割符等等)。 
而調用解構函式可以一般採用前置長度資訊的方式(當然也可以有其他方式)。在沒有[]提示時,編譯器在調用析構就將它當一個元素,而不會使用數組方式來調用每一個解構函式了。 
LS:“但是有個前提:物件類型(或其基類)有顯式解構函式.換句話說,解構函式是非trivial的. 否則的話,數組前面是沒有這個日誌的.” 
——這說明,LS使用的編譯器在釋放數組空間時,並沒有用前置的長度資訊的方式。由此可見,釋放數組空間和為數組調用解構函式確實可以使用不同的機制 

(4)delete parray,編譯器得到類型資訊是Account單個的指標,那麼釋放時,只調用一次解構函式。 
delete[] parray,編譯器得到的類型資訊是Account[]類型,則按照Account數組來處理,依次調用每個元素的解構函式。 

注意,以上是在編譯期間就確定下來的,編譯器識別到類型資訊的不同會決定調用解構函式的情況有不同。 

但是對於記憶體釋放,delete操作則不是通過類型資訊來確定分配的記憶體大小,那麼記憶體大小的資訊從什麼地方得到呢? 
當我們使用operator new為一個自訂類型對象分配記憶體時,實際上我們得到的記憶體要比實際對象的記憶體大一些,這些記憶體除了要儲存物件資料外,還需要記錄這片記憶體的大小,此方法稱為   cookie。這一點上的實現依據不同的編譯器不同。(例如   MFC   選擇在所分配記憶體的頭部儲存物件實際資料,而後面的部分儲存邊界標誌和記憶體大小資訊。g++   則採用在所分配記憶體的頭4個自己儲存相關資訊,而後面的記憶體儲存物件實際資料。)當我們使用   delete   operator   進行記憶體釋放操作時,delete  
operator   就可以根據這些資訊正確的釋放指標所指向的記憶體塊。 
對於parray指標,可以根據這樣的cookie資訊來得到指向記憶體空間的大小,delete parray和delete[] parray都是一樣的,同樣一個指標,cookie資訊是相同的,所以對應的記憶體都會被釋放掉。但是由於編譯器理解兩種情況下的類型是不同的,所以調用解構函式會有不同。 

(5)難道是編譯器輔助行為? 
沒錯,就是。不同的編譯器可能採用的具體方法有可能不一樣,但不管採用什麼方法,編譯器必須記住那塊大小。 

(6)轉自<<effective c++>> 
條款5:對應的new和delete要採用相同的形式 

下面的語句有什麼錯? 
string *stringarray = new string[100]; 
delete stringarray; 
一切好象都井然有序——一個new對應著一個delete——然而卻隱藏著很大的錯誤:程式的運行情況將是不可預測的。至少,stringarray指向的100個string對象中的99個不會被正確地摧毀,因為他們的解構函式永遠不會被調用。 
用new的時候會發生兩件事。首先,記憶體被分配(通過operator new 函數,詳見條款7-10和條款m8),然後,為被分配的記憶體調用一個或多個建構函式。用delete的時候,也有兩件事發生:首先,為將被釋放的記憶體調用一個或多個解構函式,然後,釋放記憶體(通過operator delete 函數,詳見條款8和m8)。對於 delete來說會有這樣一個重要的問題:記憶體中有多少個對象要被刪除?答案決定了將有多少個解構函式會被調用。 
這個問題簡單來說就是:要被刪除的指標指向的是單個對象呢,還是對象數組?這隻有你來告訴delete。如果你在用delete時沒用括弧,delete就會認為指向的是單個對象,否則,它就會認為指向的是一個數組: 
string *stringptr1 = new string; 
string *stringptr2 = new string[100]; 
... 
delete stringptr1;// 刪除一個對象 
delete [] stringptr2;// 刪除對象數組 
如果你在stringptr1前加了"[]"會怎樣呢?答案是:那將是不可預測的; 
如果你沒在stringptr2前沒加上"[]"又會怎樣呢?答案也是:不可預測。 
int這樣的固定類型來說,結果也是不可預測的,即使這樣的類型沒有解構函式。所以,解決這類問題的規則很簡單:如果你調用new時用了[],調用delete時也要用[]。如果調用new時沒有用[],那調用delete時也不要用[]。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.