条款16:成对使用new和delete时,采取相同的形式

问题聚焦:
    我们都知道,new和delete要成对使用,但是有时候,事情往往不是按我们预期的那样发展。
    对于单一对象和对象数组,我们要分开考虑。
    遇到typedef时,也需要搞清楚,是单一对象类型还是对象数组类型。

来看一个例子:
std::string* stringArray = new std::string[100];
...
delete stringArray;

  问题:stringArray所含的100个string对象中的99个可能并没有被适当地删除,因为它们的析构函数很可能没有被调用。


我们来了解一下,使用new时发生了什么,一共有两个动作:
  1. 内存被分配出来
  2. 针对此内存会有一个或更多个构造函数被调用
使用delete,也有两个动作:
  1. 针对此内存会有一个或更多个析构函数被调用
  2. 内存被释放
这个问题的关键:
delete的最大问题在于,即将被删除的内存之内究竟有多少个对象?这个问题的答案决定了有多少个析构函数必须被调用。
简单地说就是:
即将被删除的那个指针,所指的是单一对象,还是对象数组。因为单一对象的内存布局和对象数组的内存布局是不一样的。更明确的说,数组所用的内存通常还包括“数组大小”的记录,以便delete知道需要调用多少次析构函数。单一对象的内存则没有这笔记录。
你可以把两种不同的内存布局想象如下的形式,其中n是数组的大小:
条款16:成对使用new和delete时,采取相同的形式

当你对着一个指针使用delete,唯一能够让delete知道内存中是否存在一个“数组大小记录”的方法就是:有你来告诉它。如果你使用delete时加上中括号,delete便认定指针指向一个数组,否则它便认定指针指向单一对象。
像下面的代码:
std::string* stringPtr1 = new std::string;
std::string* stringPtr2 = new std::string[100];
...
delete stringPtr1;
delete[] stringPtr2;

  如果对stringPtr1使用delete[]形式,delete会读取若干内存并将它解释为“数组大小”,然后开始多次调用析构函数。这是很危险的,同时编译器是不会帮你检查的。

如果对stringPtr2使用delete形式,这会导致较少的析构函数被调用,这种错误对于内置类型int也是不可以的,虽然这种类型没有析构函数。


所以游戏规则很简单:

如果你调用new时,使用new[] 的形式,那么对应调用delete时也使用[],
如果你调用delete时,没有使用new[] 的形式,那么对应调用delete时也不应该使用[] 形式。

当使用typedef时,要变得尤为敏感,因为你必须要明确地知道,这时的new是一个什么样的形式:
typedef std::string AddressLines[4];       // 每个人的地址有4行,每行是一个string,typedef用于掩饰复合类型

// 这时候AddresLines是一个数组,所以new时,应该是[]形式
std:string* pal = new AddresLines; // 相当于:new string[4];
//那么,必须匹配数组形式的delete
delete pal; // error,行为未定义
delete[] pal; // pass

  所以,最好尽量不要对数组形式使用typedef动作。


 最后在看360的一道在线笔试题目:
假定指针变量p定义为“int *p=new int(100);”,要释放p所指向的动态内存,应使用语句delete p还是delete[] p。

  这个问题很简单,注意int后面是小括号,所以:

1.new int[] 是创建一个int型数组,数组大小是在[]中指定,例如:
int * p = new int[10]; //p执行一个长度为10的int数组。 2. new int()是创建一个int型数,并且用()括号中的数据进行初始化,例如:
int *p = new int(10); // p指向一个值为10的int数。

  所以正确的形式是delete p;


有关这个知识点再来看一道面试题:
使用 char* p = new char[100]申请一段内存,然后使用delete p释放,有什么问题?

不会有内存泄露,但不建议用

  当用delete来释放用new int[]申请的内存空间时,由于其为基本数据类型没有析构函数,所以使用delete与delete []相同,两者都会释放申请的内存空间,若是自定义的数据类型,有析构函数时,用new []申请的空间,必须要用delete []来释放,因为要delete []时会逐一调用对象数组的析构函数,然后释放空间。总而言之就是delete某一个指针都能够回收申请的内存空间,但是对于自定义的数据类型,要分别调用每一个对象的析构函数,所以在收回内存空间之前要先挨个的调用析构函数。delete[]的作用就是,先根据前面的n确定有几个对象,然后确定每个对象的边界,依次调用它们的析构函数。

上一篇:link 标签


下一篇:Features for configuring JSON-to-Java mapping