• 为何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:”!那么,相信答案就在你心中!

           哈哈……我发现没事可以自己动手实践,这些程序哦!有很多意外收获!

  • 相关阅读:
    如何正确得到某个元素的位置
    jQuery Tags Input Plugin(添加/删除标签插件)
    [转载]舒迅:产品经理必读的九步法
    不间断循环滚动代码
    jquery validate api
    jquery文件上传控件 Uploadify
    关于CSS权重问题
    css中fontfamily的中文字体
    expression解决IE6下固定定位的兼容
    模拟下拉select
  • 原文地址:https://www.cnblogs.com/sura/p/2575448.html
Copyright © 2020-2023  润新知