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


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

溫馨提示:

該文所有測試沒有特殊說明都是在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=newchar[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;
    TCCALLBACK(_RTC_Free_hook, (pUserData, 0));

    if (pUserData == NULL)
        return;

    /* block other threads */
    _mlock(_HEAP_LOCK);    
        __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數據結構的解釋 參考資料
2) http://hi.baidu.com/6908270270/blog/item/46b854248e0992358644f928.html#0
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();就是通過檢查分配鏈表(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]-----------------------------02 00 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;在調用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:”!那么,相信答案就在你心中!

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM