通常狀況下,編譯器在new的時(shí)候會(huì)返回用戶(hù)申請(qǐng)的內(nèi)存空間大小,但是實(shí)際上,編譯器會(huì)分配更大的空間,目的就是在delete的時(shí)候能夠準(zhǔn)確的釋放這段空間。
這段空間在用戶(hù)取得的指針之前以及用戶(hù)空間末尾之后存放。
實(shí)際上:blockSize = sizeof(_CrtMemBlockHeader) + nSize + nNoMansLandSize;其中,blockSize 是系統(tǒng)所分配的實(shí)際空間大小,_CrtMemBlockHeader是new的頭部信息,其中包含用戶(hù)申請(qǐng)的空間大小等其他一些信息。nNoMansLandSize是尾部的越界校驗(yàn)大小,一般是4個(gè)字節(jié)“FEFEFEFE”,如果用戶(hù)越界寫(xiě)入這段空間,則校驗(yàn)的時(shí)候會(huì)assert。
用戶(hù)new的時(shí)候分為兩種情況
A.new的是基礎(chǔ)數(shù)據(jù)類(lèi)型或者是沒(méi)有自定義析構(gòu)函數(shù)的結(jié)構(gòu)
B.new的是有自定義析構(gòu)函數(shù)的結(jié)構(gòu)體或類(lèi)
這兩者的區(qū)別是如果有用戶(hù)自定義的析構(gòu)函數(shù),則delete的時(shí)候必須要調(diào)用析構(gòu)函數(shù)
那么編譯器delete的如何知道要調(diào)用多少個(gè)對(duì)象的析構(gòu)函數(shù)呢,答案就是new的時(shí)候,如果是情況B,則編譯器會(huì)在new頭部之后,用戶(hù)獲得的指針之前多分配4個(gè)字節(jié)的空間用來(lái)記錄new的時(shí)候的數(shù)組大小,這樣delete的時(shí)候就可以取到個(gè)數(shù)并正確的調(diào)用。
下面我們就來(lái)看看下面幾個(gè)問(wèn)題:
示例一:
int *pInt = new int[10];
delete pInt;
這種情況下,由于編譯器不會(huì)再pInt之前分配4個(gè)字節(jié),所以delete的時(shí)候會(huì)直接從new的頭部信息里取得大小并釋放,此時(shí)delete與delete[]等價(jià)。
示例二:
Class A
{
public:
~A();
int a;
}
A *pA = new A[10];
delete pA;
這種情況下,pA之前的四個(gè)字節(jié)記錄了用戶(hù)申請(qǐng)的A對(duì)象的個(gè)數(shù),即10。但是在delete的時(shí)候,只調(diào)用了A[0]的析構(gòu)函數(shù),造成了后面9個(gè)對(duì)象的析構(gòu)函數(shù)沒(méi)有調(diào)用,可能造成類(lèi)的內(nèi)存泄漏。但是,問(wèn)題并沒(méi)有這么簡(jiǎn)單。系統(tǒng)在釋放對(duì)象本身的內(nèi)存的時(shí)候,認(rèn)為pA只是單獨(dú)的對(duì)象指針,則釋放的時(shí)候編譯器尋找new頭部信息的時(shí)候會(huì)把這4個(gè)字節(jié)計(jì)算進(jìn)去,顯然,得到的頭部信息是錯(cuò)的,當(dāng)然也就造成了釋放這段內(nèi)存會(huì)出錯(cuò)了。
示例三:
A *pA = new A;
delete[] pA;
這種情況與情況二類(lèi)似,delete[]會(huì)取pA前的4個(gè)字節(jié)當(dāng)做用戶(hù)申請(qǐng)對(duì)象的個(gè)數(shù),并調(diào)用析構(gòu),顯然此時(shí)就會(huì)出錯(cuò)。
綜合上述,在特定情況下,new[]分配的內(nèi)存用delete不會(huì)出錯(cuò),但是大多情況下會(huì)產(chǎn)生嚴(yán)重問(wèn)題,所以必須將new和delete,new[]和delete[]配套使用。
1. malloc/free 與 new/delete的不同之處:
- malloc與free是C++/C 語(yǔ)言的標(biāo)準(zhǔn)庫(kù)函數(shù),new/delete 是C++的運(yùn)算符。對(duì)于非內(nèi)部數(shù)據(jù)類(lèi)的對(duì)象而言,光用maloc/free 無(wú)法滿(mǎn)足動(dòng)態(tài)對(duì)象的要求。對(duì)象在創(chuàng)建的同時(shí)要自動(dòng)執(zhí)行構(gòu)造函數(shù), 對(duì)象消亡之前要自動(dòng)執(zhí)行析構(gòu)函數(shù)。由于malloc/free 是庫(kù)函數(shù)而不是運(yùn)算符,不在編譯器控制權(quán)限之內(nèi),不能夠把執(zhí)行構(gòu)造函數(shù)和析構(gòu)函數(shù)的任務(wù)強(qiáng)加malloc/free。而對(duì)于new和delete,編譯器會(huì)幫我們完成大部分操作。
- 我們?cè)谑褂胢alloc開(kāi)辟內(nèi)存時(shí),必須自己指定內(nèi)存量,也就是說(shuō),必須使用sizeof等得到對(duì)象大小的操作符,如
int *p = (int *) malloc(sizeof(int) * length);
malloc 返回值的類(lèi)型是void *,所以在調(diào)用malloc 時(shí)要顯式地進(jìn)行類(lèi)型轉(zhuǎn)換,將void * 轉(zhuǎn)換成所需要的指針類(lèi)型。malloc 函數(shù)本身并不識(shí)別要申請(qǐng)的內(nèi)存是什么類(lèi)型,它只關(guān)心內(nèi)存的總字節(jié)數(shù)。
而用new則無(wú)需用sizeof,也不用管變量類(lèi)型,編譯器會(huì)幫我們來(lái)搞定這個(gè)事情。 - 重復(fù)的去free一段內(nèi)存就會(huì)出錯(cuò),而重復(fù)去delete一段對(duì)象則不會(huì)有事。
總而言之,可以簡(jiǎn)單歸納成以下幾點(diǎn):
- new自動(dòng)計(jì)算需要分配的空間,而malloc需要手工計(jì)算字節(jié)數(shù)
- new是類(lèi)型安全的,而malloc不是,比如:
int* p = new float[2]; // 編譯時(shí)指出錯(cuò)誤
int* p = malloc(2*sizeof(float)); // 編譯時(shí)無(wú)法指出錯(cuò)誤
new operator 由兩步構(gòu)成,分別是 operator new 和 construct - operator new對(duì)應(yīng)于malloc,但operator new可以重載,可以自定義內(nèi)存分配策略,甚至不做內(nèi)存分配,甚至分配到非內(nèi)存設(shè)備上。而malloc無(wú)能為力
- new將調(diào)用constructor,而malloc不能;delete將調(diào)用destructor,而free不能。
- malloc/free要庫(kù)文件支持,new/delete則不要。
2. new/delete/new []/delete[] ’s action
- 調(diào)用new所包含的動(dòng)作:
從系統(tǒng)堆中申請(qǐng)恰當(dāng)?shù)囊粔K內(nèi)存。
若是對(duì)象,調(diào)用相應(yīng)類(lèi)的構(gòu)造函數(shù),并以剛申請(qǐng)的內(nèi)存地址作為this參數(shù)。 - 調(diào)用new[n]所包含的動(dòng)作:
從系統(tǒng)堆中申請(qǐng)可容納n個(gè)對(duì)象外加一個(gè)整型的一塊內(nèi)存;
將n記錄在額外的哪個(gè)整型內(nèi)存中。(其位置依賴(lài)于不同的實(shí)現(xiàn),有的在申請(qǐng)的內(nèi)存塊開(kāi)頭,有的在末尾);
調(diào)用n次構(gòu)造函數(shù)初始化這塊內(nèi)存中的n個(gè)連續(xù)對(duì)象。 - 調(diào)用delete所包含的動(dòng)作:
若是對(duì)象,調(diào)用相應(yīng)類(lèi)的析構(gòu)函數(shù)(在delete參數(shù)所指的內(nèi)存處);
該該內(nèi)存返回系統(tǒng)堆。 - 調(diào)用delete[]所包含的動(dòng)作:
從new[]記錄n的地方將n值找出;
調(diào)用n次析構(gòu)函數(shù)析構(gòu)這快內(nèi)存中的n個(gè)連續(xù)對(duì)象;
將這一整快內(nèi)存(包括記錄n的整數(shù))歸還系統(tǒng)堆。
3. 什么時(shí)候需要自己親自循環(huán)來(lái)delete數(shù)組中的元素?
如果你的對(duì)象數(shù)組的每個(gè)元素是對(duì)象指針(也就是說(shuō),你其實(shí)是開(kāi)辟了一個(gè)指針數(shù)組),那你就必須親自循環(huán)來(lái)delete每一個(gè)數(shù)組元素。否則的話(huà),你只須簡(jiǎn)單的用delete []a; 就OK了。
4. 我們?cè)趎ew一個(gè)數(shù)組時(shí),會(huì)有數(shù)組大小size記錄,對(duì)于數(shù)組,我們必須用delete[]來(lái)刪除對(duì)象。我們能否學(xué)習(xí)free的做法,用delete就實(shí)現(xiàn)delete[]功能?也就是說(shuō),假設(shè)A * a = new A[10],a是一個(gè)數(shù)組,那么能否用delete a;來(lái)代替delete []a;?
一般來(lái)說(shuō),因?yàn)槲覀冊(cè)趎ew對(duì)象數(shù)組時(shí),我們會(huì)記錄它的大小信息,因此,想用delete a;來(lái)實(shí)現(xiàn)delete []a;也不無(wú)可能。只要在delete a;判斷它是否有數(shù)組長(zhǎng)度值,如果有就刪除數(shù)組,如果沒(méi)有,就單單刪除對(duì)象就好。我們?cè)谟胐elete刪除一個(gè)對(duì)象時(shí),其實(shí)在堆中也會(huì)有該對(duì)象的大小,所以這樣才能保證刪除不會(huì)有誤。但是,這樣問(wèn)題就來(lái)了,以前delete[]時(shí)編譯器知道是數(shù)組,所以會(huì)去找數(shù)組長(zhǎng)度,然后用delete去逐一刪除對(duì)象(刪除對(duì)象時(shí)也必須得到每個(gè)對(duì)象的大?。F(xiàn)在如果用delete來(lái)代碼delete[] ,delete只能得到該數(shù)組的長(zhǎng)度,刪除時(shí)就無(wú)法刪除所有對(duì)象。(C++標(biāo)準(zhǔn)中把這種行為確認(rèn)為“無(wú)定義”)