通常状况下,编译器在new的时候会返回用户申请的内存空间大小,但是实际上,编译器会分配更大的空间,目的就是在delete的时候能够准确的释放这段空间。
这段空间在用户取得的指针之前以及用户空间末尾之后存放。 实际上:blockSize = sizeof(_CrtMemBlockHeader) + nSize + nNoMansLandSize;其中,blockSize 是系统所分配的实际空间大小,_CrtMemBlockHeader是new的头部信息,其中包含用户申请的空间大小等其他一些信息。nNoMansLandSize是尾部的越界校验大小,一般是4个字节“FEFEFEFE”,如果用户越界写入这段空间,则校验的时候会assert。 用户new的时候分为两种情况
A.new的是基础数据类型或者是没有自定义析构函数的结构 B.new的是有自定义析构函数的结构体或类 这两者的区别是如果有用户自定义的析构函数,则delete的时候必须要调用析构函数
那么编译器delete的如何知道要调用多少个对象的析构函数呢,答案就是new的时候,如果是情况B,则编译器会在new头部之后,用户获得的指针之前多分配4个字节的空间用来记录new的时候的数组大小,这样delete的时候就可以取到个数并正确的调用。
下面我们就来看看下面几个问题: 示例一:
int *pInt = new int[10]; delete pInt; 这种情况下,由于编译器不会再pInt之前分配4个字节,所以delete的时候会直接从new的头部信息里取得大小并释放,此时delete与delete[]等价。
示例二:
Class A { public: ~A(); int a; } A *pA = new A[10];
delete pA; 这种情况下,pA之前的四个字节记录了用户申请的A对象的个数,即10。但是在delete的时候,只调用了A[0]的析构函数,造成了后面9个对象的析构函数没有调用,可能造成类的内存泄漏。但是,问题并没有这么简单。系统在释放对象本身的内存的时候,认为pA只是单独的对象指针,则释放的时候编译器寻找new头部信息的时候会把这4个字节计算进去,显然,得到的头部信息是错的,当然也就造成了释放这段内存会出错了。
示例三:
A *pA = new A; delete[] pA; 这种情况与情况二类似,delete[]会取pA前的4个字节当做用户申请对象的个数,并调用析构,显然此时就会出错。
综合上述,在特定情况下,new[]分配的内存用delete不会出错,但是大多情况下会产生严重问题,所以必须将new和delete,new[]和delete[]配套使用。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/phymelinda/archive/2010/03/12/5373209.aspx
1. malloc/free 与 new/delete的不同之处:
总而言之,可以简单归纳成以下几点:
2. new/delete/new []/delete[] ’s action
3. 什么时候需要自己亲自循环来delete数组中的元素? 如果你的对象数组的每个元素是对象指针(也就是说,你其实是开辟了一个指针数组),那你就必须亲自循环来delete每一个数组元素。否则的话,你只须简单的用delete []a; 就OK了。 4. 我们在new一个数组时,会有数组大小size记录,对于数组,我们必须用delete[]来删除对象。我们能否学习free的做法,用delete就实现delete[]功能?也就是说,假设A * a = new A[10],a是一个数组,那么能否用delete a;来代替delete []a;? 一般来说,因为我们在new对象数组时,我们会记录它的大小信息,因此,想用delete a;来实现delete []a;也不无可能。只要在delete a;判断它是否有数组长度值,如果有就删除数组,如果没有,就单单删除对象就好。我们在用delete删除一个对象时,其实在堆中也会有该对象的大小,所以这样才能保证删除不会有误。但是,这样问题就来了,以前delete[]时编译器知道是数组,所以会去找数组长度,然后用delete去逐一删除对象(删除对象时也必须得到每个对象的大小),而现在如果用delete来代码delete[] ,delete只能得到该数组的长度,删除时就无法删除所有对象。(C++标准中把这种行为确认为“无定义”) |
|