我们从堆上申请创建一个包含 12 个整数的数组,怎么样呀?没问题:
int *ip = new int(12);
for (int i = 0; i < 12; ++i) ip[i] = i; delete [] ip;
注意我们用的那对空的中括号,它告知编译器 ip 指涉到的是一个包含一堆整数的数组,而 不是单个的一个整数。等等,事实真的是这样吗?
其实,ip 指涉到的恰恰是单个的一个整数,被初始化成了 12。我们犯了一个常见的手误, 把一对中括号打成了一对小括号。这么一来,循环体里就会出现非法访问(索引值大于 0 的部分统统如此),内存回收这句也行不通了。可是,没有几个编译器能在编译期就把这个 错误给逮出来。因为一个指针既可以指涉到单个的一个对象,也可以指涉到包含一堆对象的 数组,而且循环体内的索引语句和数组的内存回收语句在语法意义上可谓无懈可击。这么一 来,我们直到运行期才会意识到犯了错误。
也许连运行期都安然无恙。没错,访问对象数组所占空间结束之后的位置是非法的(虽然标 准保证了访问对象数组结束之后的一个对象元素是可以的)(译注:这是一个很容易被忽略 的重要补充说明。为什么要保证访问数组后的一个对象元素是能够做到的呢?这实际上是为 了和 for 语句的迭代算子习惯用法相一致,也是 STL 的“前闭后开区间”习惯用法相一致。 但是,如果用指针指涉到了这样的一个位置,它是不能被提领的。有关这个问题更详细的说 明,参见(Stroustrup, 2001),§5.3),把应用于数组的内存回收语法应用到并非数组的纯量 上也是非法的。但做了非法的事,并不意味着你就没有机会逍遥法外(想想华尔街操盘手们 干的勾当)。以上的代码在一些平台能够完美运行,但在另一些平台上则会在运行时崩溃, 在某些特定平台上还会玩出别的古怪花样来,到底会如何,就全看特定的线程或进程在运行 时是怎么操作堆内存了。正确的内存申请语法,当然如下所示:
int *ip = new int[12];
说不定,最好的内存申请形式就是根本不去自己做这个内存申请:直接用标准库中的组件 就好:
std::vector<int> iv(12); for (int i = 0; i < iv.size(); ++i) iv[i] = i; // 不用显式地回收内存(译注:也不用显式地申请内存)