通常状况下,编译器在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[]配套使用。