为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样-------_CrtMemBlockHeader
来源:互联网 发布:深圳神州通集团 知乎 编辑:程序博客网 时间:2024/04/30 19:56
为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样-------_CrtMemBlockHeader
------jiese1990
- <span style="font-size:16px;">//程序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();
- }
- </span>
2)对象数组不能用delete,只能用delete[];
首先我们需要知道:系统在释放对象数组时,会先执行数组内所有元素的析构函数,然后再调用void operator delete(void *pUserData),一次性将所有分配的数据空间释放!
- <span style="font-size:16px;">//程序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;
- }
- </span>
在release下运行,没有出现上面那个错误提示窗口!但是输出结果是一样的!数组里5个对象只有第一个对象,运行了析构函数!事实证明2的断言同样也是正确的!OK!
那么我就要问了,
delete 结构体数组----都不会出问题!而delete 对象数组----报错。为什么呢???
如果你深深的被这个疑问所困恼,那么接下来让我们一起来解放这个疑惑!这个痛苦!
有些人有这样的误解:
我在网上看了很多帖子,很多人说,程序B:delete pas;只释放了pas[0]其他的都没有释放;因为根据程序运行结果,我们可以看出,他只调用pas[0]的虚构函数!那么你怎么看呢?你觉得呢?
有人认为可以如下来释放数组所有空间:
- <span style="font-size:16px;">//程序C:
- CTextClassA *pas=new CTextClassA[5];
- for(int i=0;i<5;i++)
- {
- delete pas[i];
- }
- </span>
那么你,怎么看待程序C;你觉得这样子可以吗?答案你自己去需找!看看运行结果你就会知道!异或是看完全文,那么你也会明白!
Ok!debug模式运行程序B,弹出上面错误提示框!按下重试,进入出错函数里
- <span style="font-size:16px;">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;
- }
- </span>
参考资料
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:
- <span style="font-size:16px;">//程序D:
- int *pis=new int[5];
- for(int i=0;i<5;i++)
- {
- pis[i]=i;
- }
- delete[] pis;</span>
内存窗口有关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内存数据
- <span style="font-size:16px;">//程序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;//修改部分
- </span>
程序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:”!那么,相信答案就在你心中!
哈哈……我发现没事可以自己动手实践,这些程序哦!有很多意外收获!
- 为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样-------_CrtMemBlockHeader
- 为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样-------_CrtMemBlockHeader
- 为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样-------_CrtMemBlockHeader
- 为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样-------_CrtMemBlockHeader
- 为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样-------_CrtMemBlockHeader
- 为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样
- 为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样
- new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样
- new出的对象数组必须要用delete[]删除,而普通数组和结构数组delete和delete[]都一样
- new和delete数组类型
- new/delete和对数组的重命名
- 二维数组的new 和 delete
- 用于数组的new和delete
- c++ 的用 new 和delete 进行二维数组动态内存的创建和删除
- C++ 用new 、delete 动态创建、删除数组
- C++里 数组new 和delete问题
- 使用new和delete创建二维数组
- 数组的动态分配(new)、初始化(memset)和撤销(delete)
- UVA1455 - Kingdom(并查集 + 线段树)
- 安卓训练-目录
- http和https有什么不同
- HDU2037 今年暑假不AC 贪心
- 内核模块的加载与移除
- 为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样-------_CrtMemBlockHeader
- yaffs2根文件系统制作教程
- Android Studio快捷键
- ubuntu JDK7 安装详细教程(pycharm必备)
- 从solaris升级到AIX6.1 ACE Service_Types.h build error
- Mysql 一句SQL用数据库字段更新指定字段
- Quick-Cocos2d-X 核心framework 目录结构与功能介绍
- 唐彬:互联网进入深水区挑战与机遇
- Cocos2d-x_CCAction(常用动作类)介绍