为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样-------_CrtMemBlockHeader

------jiese1990

温馨提示:
该文所有测试没有特殊说明都是在Debug模式下!用的是VS2010编译器!
 1.在释放堆栈中c++基本数据(包括int,char.....结构体等)的存储空间时,不管是否是数组用delete都不会有错!而且能正常释放所有内存,不会导致内存泄露!
[cpp] view plaincopy
  1. <span style="font-size:16px;">//程序A
  2. struct text_data_t
  3. {
  4. int i;
  5. };
  6. int _tmain(int argc, _TCHAR* argv[])
  7. {
  8. text_data_t *pdata=new text_data_t[5];
  9. char *pi=new char[5];
  10. for(int k=0;k<5;k++)
  11. pdata[k].i=k;
  12. delete pdata;
  13. delete pi;
  14. //内存泄露检测函数。若检测到内存泄漏那么就会输出宽里输出?"Detected memory leaks!....等信息"
  15. _CrtDumpMemoryLeaks();
  16. }
  17. </span>
没有检测到内存泄露,于是乎,可以看出1是正确的!

2)对象数组不能用delete,只能用delete[];


         首先我们需要知道:系统在释放对象数组时,会先执行数组内所有元素的析构函数,然后再调用void operator delete(void *pUserData),一次性将所有分配的数据空间释放!

[cpp] view plaincopy
  1. <span style="font-size:16px;">//程序B
  2. class CTextClassA
  3. {
  4. public:
  5. int m_num;
  6. CTextClassA(){m_num=0;};
  7. ~CTextClassA()
  8. {
  9. cout<<"~CTextClassA()"<<endl;
  10. }
  11. void SetNum(int n)
  12. {
  13. m_num=n;
  14. }
  15. };
  16. int _tmain(int argc, _TCHAR* argv[])
  17. {
  18. //类¤¨¤
  19. CTextClassA *pa=new CTextClassA;
  20. CTextClassA *pas=new CTextClassA[5];
  21. CTextClassA *pas_arr[5];
  22. for(int i=0;i<5;i++)
  23. {
  24. pas[i].SetNum(i);
  25. pas_arr[i]=&pas[i];
  26. cout<<"pas"<<i<<":"<<pas[i].m_num<<"\t";
  27. }
  28. delete pa;
  29. delete pas;
  30. }
  31. </span>

输出结果

调试运行到delete pas;出现保护错!

在release下运行,没有出现上面那个错误提示窗口!但是输出结果是一样的!数组里5个对象只有第一个对象,运行了析构函数!事实证明2的断言同样也是正确的!OK!

那么我就要问了,

           delete 结构体数组----都不会出问题!而delete 对象数组----报错。为什么呢???

如果你深深的被这个疑问所困恼,那么接下来让我们一起来解放这个疑惑!这个痛苦!

有些人有这样的误解:

我在网上看了很多帖子,很多人说,程序B:delete  pas;只释放了pas[0]其他的都没有释放;因为根据程序运行结果,我们可以看出,他只调用pas[0]的虚构函数!那么你怎么看呢?你觉得呢?

有人认为可以如下来释放数组所有空间:

[cpp] view plaincopy
  1. <span style="font-size:16px;">//程序C:
  2. CTextClassA *pas=new CTextClassA[5];
  3. for(int i=0;i<5;i++)
  4. {
  5. delete pas[i];
  6. }
  7. </span>

那么你,怎么看待程序C;你觉得这样子可以吗?答案你自己去需找!看看运行结果你就会知道!异或是看完全文,那么你也会明白!

Ok!debug模式运行程序B,弹出上面错误提示框!按下重试,进入出错函数里

[cpp] view plaincopy
  1. <span style="font-size:16px;">void operator delete(
  2. void *pUserData
  3. )
  4. {
  5. _CrtMemBlockHeader * pHead;
  6. RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
  7. if (pUserData == NULL)
  8. return;
  9. _mlock(_HEAP_LOCK);  /* block other threads */
  10. __TRY
  11. /* get a pointer to memory block header */
  12. pHead = pHdr(pUserData);
  13. /* verify block type */
  14. _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));//程序中断处,按F11进入不了函数内部!那怎么办呢,从数据pHead入手
  15. _free_dbg( pUserData, pHead->nBlockUse );
  16. __FINALLY
  17. _munlock(_HEAP_LOCK);  /* release other threads */
  18. __END_TRY_FINALLY
  19. return;
  20. }
  21. </span>

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:

[cpp] view plaincopy
  1. <span style="font-size:16px;">//程序D:
  2. int *pis=new int[5];
  3. for(int i=0;i<5;i++)
  4. {
  5. pis[i]=i;
  6. }
  7. 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内存数据

[cpp] view plaincopy
  1. <span style="font-size:16px;">//程序E:
  2. CTextClassA *pa=new CTextClassA;
  3. CTextClassA *pas=new CTextClassA[5];
  4. CTextClassA *pas_arr[5];
  5. for(int i=0;i<5;i++)
  6. {
  7. pas[i].SetNum(i);
  8. pas_arr[i]=&pas[i];
  9. cout<<"pas"<<i<<":"<<pas[i].m_num<<"\t";
  10. }
  11. delete pa;
  12. delete[] pas;//修改部分
  13. </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的数据可不一样哦!

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

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

为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样-------_CrtMemBlockHeader相关推荐

  1. B10_NumPy数组操作、修改数组形状、翻转数组、修改数组维度、连接数组、分割数组、数组元素的添加与删除

    NumPy数组操作 Numpy 中包含了一些函数用于处理数组,大概可分为以下几类: 修改数组形状 翻转数组 修改数组维度 连接数组 分割数组 数组元素的添加与删除 修改数组形状 函数 描述 resha ...

  2. python ndarray append_9-Python-NumPy数组元素的添加与删除

    数组元素的添加与删除 相关函数列表如下: 函数 元素及描述 resize 返回指定形状的新数组 append 将值添加到数组末尾 insert 沿指定轴将值插入到指定下标之前 delete 删掉某个轴 ...

  3. 数组维度超过了支持的范围_数组公式基础:多值和单值结果_

    Excel公式教程 (2016-01-31 22:00:04) 转载 ▼ 标签: 分类: 数组公式什么情况下返回数组?什么情况下返回单值? 数组运算后返回一个数组,所以数组公式可以返回数组结果.如图: ...

  4. 扩展JavaScript数组(Array)添加删除元素方法

    为JavaScript数组(Array)扩展 添加删除元素方法 作者:jcLee95:https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343 ...

  5. java数组有跨类建立对象_必会的 55 个 Java 性能优化细节!一网打尽!

    程序员的成长之路互联网/程序员/成长/职场 关注 阅读本文大概需要 10 分钟. 来源:https://yq.aliyun.com/articles/662001 在 Java 程序中,性能问题的大部 ...

  6. 回调函数必须要用static的原因

    在之前的一篇回调函数简单例子中就写了一个简单的回调小例子,这里补充一下. 一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果.this作用域是在类内部,当在类的非静态成员 ...

  7. java 静态对象数组_Java静态方法和实例方法 java中的数组作为形参传入

    原标题:Java静态方法和实例方法 java中的数组作为形参传入 Java静态方法和实例方法 java中的数组作为形参传入 Java虚拟机 启动一个Java程序的时候,会诞生一个虚拟机实例,当程序关闭 ...

  8. (转载)非常好 必须要顶

    关键类 1. Activity 2. Service 3. BroadcastReceiver 4. ContentProvider 5. Intent Android应用程序使用Java做为开发语言 ...

  9. JavaScript笔记6(数组,foreach(),slice(),splice(),数组的剩余方法,函数的方法call和apply ,arguments,Date对象,Math,字符串的相关方法)

    JavaScript 数组(Array) forEach(),slice(),splice() 数组的剩余方法 函数的方法call()和apply() arguments Date对象 Math 字符 ...

最新文章

  1. 7.2 PCA-机器学习笔记-斯坦福吴恩达教授
  2. EMNLP 2021 | PairSupCon:基于实例对比学习的句子表示方法
  3. VTK:图像加权和用法实战
  4. hadoop删除节点
  5. crtsiii型无砟轨道板_自主知识产权CRTSⅢ型轨道板助力,赣深铁路无砟轨道轨道板灌注第一工作面完成...
  6. Mac安装软件报“打不开。。。,因为它来自身份不明的开发者”的解决办法
  7. SpringCloud Eureka参数配置项详解
  8. 终于可以自定义喇叭声音:你的特斯拉可以“放屁”吓唬人了
  9. 从 Flash 到 WEBGPU,Web 图形技经历过的变革你了解吗?
  10. 2018-10-05学习笔记
  11. 我们可以拥有多少级指针?
  12. 编译原理完整学习笔记(六):语义分析和中间代码生成
  13. ffmpeg实现视频切割
  14. 并发编程之 ThreadLocal 源码剖析
  15. 40G/100G万兆交换机如何选择?
  16. 有关JAVA考试中数据库的题_全国2018年4月自考互联网数据库考试真题
  17. 怎么更改电脑桌面文件存放路径
  18. Servlet学习之Servlet概念与运行流程
  19. 交通灯控制器的verilog实现
  20. 2020保研夏令营回顾--清华网研院+清华深研院

热门文章

  1. python 配置文件解析_python 解析配置文件
  2. python 共享内存_37. Python 多进程锁 多进程共享内存
  3. 十个多线程并发编程面试题(附答案)
  4. Android学习笔记(十二)——Fragment向Activity传递消息
  5. Soft NMS算法笔记
  6. JS获取服务上下文,兼容上下文为空场景
  7. Bootstrapbutton组
  8. Linux故障之grub
  9. javax.servlet.http.HttpServlet was not found
  10. Js 通过点击改变css样式