爲何new出的對象數組必須要用delete[]刪除,而普通數組delete和delete[]都一樣-------_CrtMemBlockHeader

                       爲何new出的對象數組必須要用delete[]刪除,而普通數組delete和delete[]都一樣-------_CrtMemBlockHeader

                                                                                                                              ------jiese1990

溫馨提示:
該文所有測試沒有特殊說明都是在Debug模式下!用的是VS2010編譯器!

 1.在釋放堆棧中c++基本數據(包括int,char.....結構體等)的存儲空間時,不管是否是數組用delete都不會有錯!而且能正常釋放所有內存,不會導致內存泄露!
//程序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();
}
沒有檢測到內存泄露,於是乎,可以看出1是正確的!


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;
}
輸出結果
調試運行到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();就是通過檢查分配鏈表(pBlockHeaderNextpBlockHeaderPrev爲雙鏈表的雙鏈), 來查找是否有泄漏。

 

 

我搜索了很多量資料,做了很多實驗,得出結論:

對於普通數據存儲空間的分配形式:

         公式1)_CrtMemBlockHeader + <Your Data> +gap[nNoMansLandSize];這類數據用deletedelete[]都一樣!

通常我們的指針都是指向<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的數據可不一樣哦!

程序B:;
程序E:;


大家對比下數據....仔細觀察,又沒有發現什麼倪端?沒發現嗎?再仔細看看!看出來了吧!哈哈,答案,就在你心中!
先看程序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:”!那麼,相信答案就在你心中!

       哈哈……我發現沒事可以自己動手實踐,這些程序哦!有很多意外收穫!





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章