参看链接
浅谈 C++ 中的 new/delete 和 new[]/delete[]
operator new 和 operator delete
这两个其实是 C++ 语言标准库的库函数,原型分别如下:
void *operator new(size_t); //allocate an object
void *operator delete(void *); //free an object
void *operator new[](size_t); //allocate an array
void *operator delete[](void *); //free an array
new 和 delete 背后机制
class A
{
public:
A(int v) : var(v)
{
fopen_s(&file, "test", "r");
}
~A()
{
fclose(file);
}
private:
int var;
FILE *file;
};
class A *pA = new A(10);
来创建一个类的对象,返回其指针pA
。如下图所示 new
背后完成的工作:
总结一下:
- 首先需要调用上面提到的 operator new 标准库函数,传入的参数为
class A
的大小,这里为 8 个字节,至于为什么是 8 个字节,你可以看看《深入 C++ 对象模型》一书,这里不做多解释。这样函数返回的是分配内存的起始地址,这里假设是0x007da290
。
上面分配的内存是未初始化的,也是未类型化的, - 第二步就在这一块原始的内存上对类对象进行初始化,调用的是相应的构造函数,这里是调用
A:A(10);
这个函数,从图中也可以看到对这块申请的内存进行了初始化,var=10
, `file 指向打开的文件。 - 最后一步就是返回新分配并构造好的对象的指针,这里 pA 就指向
0x007da290
这块内存,pA
的类型为类A
对象的指针。
delete pA;
delete
所做的事情如下图所示:
delete
就做了两件事情:
- 调用 pA 指向对象的析构函数,对打开的文件进行关闭。
- 通过上面提到的标准库函数
operator delete
来释放该对象的内存,传入函数的参数为pA
的值,也就是0x007d290
如何申请和释放一个数组?
在 new []
一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个字节 的大小,专门保存数组的大小,在 delete []
时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。
调用
class A *pAa = new A[3];
时需要做的事情如下:
delete []pAa;
这里要注意的两点是:
- 调用析构函数的次数是从数组对象指针前面的 4 个字节中取出;
- 传入 operator delete[] 函数的参数不是数组对象的指针 pAa,而是 pAa 的值减 4。
为什么 new/delete 、new []/delete[] 要配对使用?
如果是带有自定义析构函数的类类型,用 new []
来创建类对象数组,而用 delete
来释放会发生什么?用上面的例子来说明:
class A *pAa = new class A[3];
delete pAa;
那么 delete pAa;
做了两件事:
- 调用一次
pAa
指向的对象的析构函数; - 调用
operator delete(pAa);
释放内存。
显然,这里只对数组的第一个类对象调用了析构函数,后面的两个对象均没调用析构函数,如果类对象中申请了大量的内存需要在析构函数中释放,而你却在销毁数组对象时少调用了析构函数,这会造成内存泄漏。
上面的问题你如果说没关系的话,那么第二点就是致命的了!直接释放pAa
指向的内存空间,这个总是会造成严重的段错误,程序必然会奔溃!因为分配的空间的起始地址是 pAa
指向的地方减去 4 个字节的地方。你应该传入参数设为那个地址!