new/delete 和 new[]/delete[]
本文以如下的base为例,进行调试和验证,new 和delete,以及new[]和delete[]都是标准库函数,通过重载上述四个函数。
1 class base{
2 public:
3 base(void){
4 printf("base\n");
5 }
6
7 static void* operator new(size_t cb,int nBlockUse,const char * szFileName,int nLine){
8 printf("operator new\n");
9 return ::operator new(cb);
10 }
11
12 static void* operator new[](size_t cb,int nBlockUse,const char * szFileName,int nLine){
13 printf("operator new[]\n");
14 return ::operator new(cb);
15 }
16
17 void operator delete(void* p,size_t ){
18 printf("operator delete\n");
19 }
20
21 void operator delete[](void* p,size_t ){
22 printf("operator delete[]\n");
23 }
24
25 ~base(void){
26 printf("~base\n");
27 }
28 };
1 new和delete表达式
1.1 new 表达式
base* pb = new base;
执行new表达式时,实际发生了三个步骤:
- 首先,调用名为operator new的标准库函数(可以重载),其功能是分配足够大的原始类型的内存,以保存指定类型的对象;
- 然后,运行该类型的一个构造函数,用指定初始化式构造对象;
- 最后,返回指向新分配并构造的对象指针。
上述语句输出的是:
operator new
base
1.2 delete表达式
delete pb;
执行delete表达式时,实际发生了两个步骤:
- 首先,对指针指向的对象运行适当的析构函数;
- 然后,通过调用名为operator new的标准库函数(可以重载)释放该对象所用内存。
上述语句输出的是:
~base
operator delete
2 new[]和delete[]表达式
2.1 new[]表达式
base* pb = new base[3];
执行new[]表达式时,实际上发生了三个步骤:
- 首先,调用名为operator new[] 的标准库函数(可以重载)。其功能是分配足够大的原始类型的内存,并在最低地址用四个字节来保存创建数组的大小。
- 然后,通过上述数组的大小,运行该类型的多个构造函数;
- 最后,返回数组的是数组对象的指针,而不是所有分配对象空间的的起始地址。
上述语句输出:
operator new[]
base
base
base
2.2 delete[]表达式
delete[] p;
执行delete[]表达式,实际上发生了两个步骤:
- 首先,根据数组最低地址的4个字节空间,获得数组的大小,然后调用多个析构函数;
- 然后,调用operator delete[] 标准库函数(可以重载)释放该对象所有内存。
上述语句输出:
operator new[]
base
base
base
注意:只有在中括号出现时,编译器才寻找数组的维度,否则它便假设只有单独一个objects要被删除。
3 特殊出现
正确使用的配对是new与delete,以及new[]与delete[]成对出现,这样才能保证内存空间的正确释放和析构函数正确的调用,但有时也会出现它们之间不是正确的配对,如下几种情况
3.1 new与delete[]错误配对
base* pb = new base;
delete[] pb;
输出:
operator new
base
~base
operator delete
从上述结果得到:当new一个对象,而用delete[]进行释放对象时,这与正常new和delete配对使用时,输出的内容是一样的,即释放的对象。
3.2 new[]与delete错误配对
base* p = new base[3];
delete p;
输出:
operator new[]
base
base
base
~base
operator delete
从上述结果得到:当使用new[]创建了多个对象,但是使用delete释放对象时,只释放的第一个对象。其它元素仍然存在——虽然其相关的内存已经被要求归还了。