爲何new出的對象數組必須要用delete[]刪除,而普通數組delete和delete[]都一樣-------_CrtMemBlockHeader
------jiese1990
//程序A
struct text_data_t
{
int i;
};
int _tmain(int argc, _TCHAR* argv[])
{
text_data_t *pdata=new text_data_t[5];
char *pi=new char[5];
for(int k=0;k<5;k++)
pdata[k].i=k;
delete pdata;
delete pi;
//內存泄露檢測函數。若檢測到內存泄漏那麼就會輸出寬裏輸出?"Detected memory leaks!....等信息"
_CrtDumpMemoryLeaks();
}
2)對象數組不能用delete,只能用delete[];
首先我們需要知道:系統在釋放對象數組時,會先執行數組內所有元素的析構函數,然後再調用void operator delete(void *pUserData),一次性將所有分配的數據空間釋放!
//程序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,彈出上面錯誤提示框!按下重試,進入出錯函數裏
void operator delete(
void *pUserData
)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));//程序中斷處,按F11進入不了函數內部!那怎麼辦呢,從數據pHead入手
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
Google找到pHead 的類型CrtMemBlockHeader數據結構的解釋參考資料
1) http://msdn.microsoft.com/en-us/library/bebs9zyz(v=vs.71).aspx
2) http://hi.baidu.com/6908270270/blog/item/46b854248e0992358644f928.html#0
typedefstruct _CrtMemBlockHeader
{
// Pointer to the block allocatedjust before this one:指向前一塊數據塊的指針?
struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocatedjust after this one:指向下一塊數據塊的指針
struct _CrtMemBlockHeader *pBlockHeaderPrev;
char *szFileName; // File name存儲的發起分配操作的那行代碼所在的文件的路徑和名稱,但實際上是空指針?
int nLine; // Line number則是行號?,也就是存儲的啓發分配操作代碼的行號?
size_t nDataSize; // Size of user block請求分配的大小?(對象數組爲sizeof(數組元素個數-----這裏也就是個unsigned int類型)+ sizeof(<You Data>))
int nBlockUse; // Type of block 類型
long lRequest; // Allocation number請求號
// Buffer just before (lower than)the user's memory:lRequest
unsigned char gap[nNoMansLandSize];//這個數據是幹嘛的呢,查下單詞gap是什麼意思,你就知道了!
} _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:
//程序D:
int *pis=new int[5];
for(int i=0;i<5;i++)
{
pis[i]=i;
}
delete[] pis;
內存窗口有關pis內存數據的內容:
從上圖數據可以看出
pis[0]在內存裏的存儲數據爲00 00 00 00
pis[1]-----------------------------01 00 00 00(由於我的計算機是Intel,用的是 LittleEndian,所以低位在前高位在後,所以該真正的值爲00 00 00 01==1)
pis[2]------------------------------0200 00 00(------------------00 00 0002==2)
........就不一一列舉了
如圖可以看出對於,公式1)是正確的!如果你不信的話可以自己調試下看看!而且證明確實分配的不僅僅只有<your data>!
程序B稍加修改,查看pas內存數據
//程序E:
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;//修改部分
程序E內存數據:
程序E:進入void operator delete( void *pUserData),查看監視窗口下pHead的值!
事實證明公式2也是正確的!
OK,由此可證,普通數組和對象的數組存儲結構不同,那麼會不會就是因爲這結構不同導致delete上的不同差異呢?
也就是說是不是,正是因爲他這多出來的一個數組元素個數(_CrtMemBlockHeader +數組元素個數+ <You Data> + gap[nNoMansLandSize];)導致delete的差異!
那麼是不是這樣呢?究竟是不是介樣呢?
好吧,讓我們再做個試驗來驗證下:
在此運行程序B,進入void operatordelete( void*pUserData)觀察內存數據和pHead數據的值:
pas內存數據和程序E一樣,然而pHead的數據可不一樣哦!
先看程序E:pHead的地址剛好比程序B:的地址大4位!這就是癥結所在!你觀察兩份數據也很容易看出:
程序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;在調用voidoperatordelete(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:”!那麼,相信答案就在你心中!
哈哈……我發現沒事可以自己動手實踐,這些程序哦!有很多意外收穫!