為何new出的對象數組必須要用delete[]刪除,而普通數組delete和delete[]都一樣-----_CrtMemBlockHeader
該文所有測試沒有特殊說明都是在Debug模式下!用的是VS2010編譯器!
//程序A struct text_data_t { int i; }; int _tmain(int argc, _TCHAR* argv[]) { text_data_t *pdata=new text_data_t[5]; char *pi=newchar[5]; for(int k=0;k<5;k++) pdata[k].i=k; delete pdata; delete pi; //內存泄露檢測函數。若檢測到內存泄漏那么就會輸出寬里輸出?"Detected memory leaks!....等信息" _CrtDumpMemoryLeaks(); }

// 程序B class CTextClassA { public: int m_num; CTextClassA() { m_num=0; }; ~CTextClassA() { cout<<"~CTextClassA()"<<endl; } void SetNum(int n) { m_num=n; } }; int _tmain(int argc, _TCHAR* argv[]) { CTextClassA *pa=new CTextClassA; CTextClassA *pas=new CTextClassA[5]; CTextClassA *pas_arr[5]; for(int i=0;i<5;i++) { pas[i].SetNum(i); pas_arr[i]=&pas[i]; cout<<"pas"<<i<<":"<<pas[i].m_num<<"\t"; } delete pa; delete pas; }
輸出結果


在release下運行,沒有出現上面那個錯誤提示窗口!但是輸出結果是一樣的!數組里5個對象只有第一個對象,運行了析構函數!事實證明2的斷言同樣也是正確的!OK!
====================================================================================================================================
那么我就要問了,
delete 結構體數組----都不會出問題!而delete 對象數組----報錯。為什么呢???
如果你深深的被這個疑問所困惱,那么接下來讓我們一起來解放這個疑惑!這個痛苦!
有些人有這樣的誤解:
我在網上看了很多帖子,很多人說,程序B:delete pas;只釋放了pas[0]其他的都沒有釋放;因為根據程序運行結果,我們可以看出,他只調用pas[0]的虛構函數!那么你怎么看呢?你覺得呢?
有人認為可以如下來釋放數組所有空間:
//程序C: CTextClassA *pas=new CTextClassA[5]; for(int i=0;i<5;i++) { delete pas[i]; }
那么你,怎么看待程序C;你覺得這樣子可以嗎?答案你自己去需找!看看運行結果你就會知道!異或是看完全文,那么你也會明白!
Ok!debug模式運行程序B,彈出上面錯誤提示框!按下重試,進入出錯函數里
Google找到pHead 的類型CrtMemBlockHeader數據結構的解釋 參考資料typedefstruct _CrtMemBlockHeader { // 指向前一塊數據塊的指針 struct _CrtMemBlockHeader *pBlockHeaderNext; // 指向下一塊數據塊的指針 struct _CrtMemBlockHeader *pBlockHeaderPrev; // File name:請求內存分配操作的那行代碼所在的文件的路徑和名稱,但實際上是空指針 char *szFileName; // Line number:行號,請求內存分配操作的那行代碼的行號 int nLine; // 請求分配的大小
size_t nDataSize; // Type of block 類型 int nBlockUse; // 請求號 long lRequest; // 這個數據是干嘛的呢,查下單詞gap是什么意思,你就知道了! unsigned char gap[nNoMansLandSize]; } _CrtMemBlockHeader;
這里解釋下Gap[]吧:
<your data>前后各有4個字節的 gap[],前后的gap都為 0xFD.
如果你在自己的Data里寫, 不小心越了界(前面或者后面), 系統在delete的時候通過檢查 gap 的數據是否還為0xFD,就知道你有沒有越界.
當然了, 如果你恰好寫的都是0xFD, 那就沒法知道了.
函數_CrtDumpMemoryLeaks();就是通過檢查分配鏈表(pBlockHeaderNext和pBlockHeaderPrev為雙鏈表的雙鏈), 來查找是否有泄漏。
====================================================================================================================================
我搜索了很多量資料,做了很多實驗,得出結論:
對於普通數據存儲空間的分配形式:
公式1)_CrtMemBlockHeader + <Your Data> +gap[nNoMansLandSize];這類數據用delete和delete[]都一樣!
通常我們的指針都是指向<your data>的首地址!
而對於對象數組則是:
公式2)_CrtMemBlockHeader +數組元素個數+ <Your Data> +gap[nNoMansLandSize];
舉個例子說:
int *pis=new int[5];
當我們的程序執行到這么一條語句時,你覺得系統會給他分配多少內存空間,20?如果你的答案是20那么我可以告訴你,親,你太單純了,想得太簡單了!那么請再仔細理解前面兩個公式!
實際上系統分配sizeof(CrtMemBlockHeader)+20+sizeof(gap[nNoMansLandSize])大小的空間!
而CTextClassA*pas=new CTextClassA[5];
則分配sizeof(CrtMemBlockHeader)+4(該空間,用來存儲數組中元素數目大小,占用4Byte)+20+sizeof(gap[nNoMansLandSize])大小的空間!
OK,也許你不相信我得出的這個結論!我早有准備!
調試運行如下代碼,並打開內存窗口觀察pis:
內存窗口有關pis內存數據的內容:

從上圖數據可以看出
pis[0]在內存里的存儲數據為00 00 00 00
pis[1]-----------------------------01 00 00 00(由於我的計算機是Intel,用的是 LittleEndian,所以低位在前高位在后,所以該真正的值為00 00 00 01==1)
pis[2]-----------------------------02 00 00 00(------------------00 00 0002==2)
........
如圖可以看出對於,公式(1)是正確的!如果你不信的話可以自己調試下看看!而且證明確實分配的不僅僅只有<your data>!
程序B稍加修改,查看pas內存數據
程序E內存數據:

事實證明公式2也是正確的!
====================================================================================================================================
OK,由此可證,普通數組和對象的數組存儲結構不同,那么會不會就是因為這結構不同導致delete上的不同差異呢?
也就是說是不是,正是因為他這多出來的一個數組元素個數(_CrtMemBlockHeader +數組元素個數+ <You Data> + gap[nNoMansLandSize];)導致delete的差異!
那么是不是這樣呢?究竟是不是介樣呢?
好吧,讓我們再做個試驗來驗證下:
在此運行程序B,進入void operatordelete( void*pUserData)觀察內存數據和pHead數據的值:
pas內存數據和程序E一樣,然而pHead的數據可不一樣哦!


程序E的pHead->pBlockHeaderPrev==程序B的pHead->pBlockHeaderNext;
程序E的pHead->szFileName==程序B的pHead->pBlockHeaderPrev;
程序E的pHead->nLine==程序B的pHead->szFileName;
程序E的pHead->nDataSize==程序B的pHead->nLine;
……再想想四位,四位不剛好是sizeof(數組元素個數)嗎?
恩,不錯,確實如此,delete pas;在調用void operato rdelete(void*pUserData)時會將<yourdata>的首地址傳給pUserData,
那么程序會將<your data>的前8*4Byte數據當成_CrtMemBlockHeader,也就是說(_CrtMemBlockHeader:: pBlockHeaderPrev開始到 數組元素個數)數據當成CrtMemBlockHeader的數據;
還記得程序B的中斷處嗎?_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));該函數是檢查,你的數據的數據類型的!而pHead->nBlockUse值已經完全變了,而且變化很大,原本應該是1的可現在是b2!當然要報錯了!不報錯才怪!然而delete[] pas;則會將 (數組元素個數+<yout data>)整個數據當成pUserData!數組元素個數數據,前8*4Byte當成_CrtMemBlockHeader,寫入到pHead!
恩看到這里相信你明白了,是腫么回事了吧!
那么回過頭來,想想,我們之前的“程序C:”!那么,相信答案就在你心中!
哈哈……我發現沒事可以自己動手實踐,這些程序哦!有很多意外收獲!