为什么 new int[n] 有效,而 int array[n] 无效

Why is new int[n] valid when int array[n] is not?

本文关键字:int array 无效 有效 new 为什么      更新时间:2023-10-16

对于以下代码:

foo(int n){
    int array[n];
}

我知道这是无效的语法,并且它是无效的,因为 c++ 标准要求在编译时设置数组大小(尽管某些编译器支持以下语法(。

但是,我也了解以下语法是有效的语法:

bar(int n){
    int *array = new int[n];
}

我不明白为什么允许这样做,这与创建在运行时确定大小的数组不同吗?这样做是很好的做法,还是如果需要这样做,我应该使用矢量?

这是因为前者在堆栈上分配,后者在堆上分配。

当您在堆栈上分配某些内容时,了解对象的大小对于正确构建它至关重要。C99 允许在运行时指定大小,这在构建和拆除上述堆栈时引入了一些复杂性,因为您无法在编译时计算其大小。必须发出机器代码才能在程序执行期间执行上述计算。这可能是此功能未包含在C++标准中的主要原因。

相反,顾

名思义,堆没有固定的结构。任何大小的块都可以以没有特定顺序进行分配,只要它们不重叠并且您有足够的(虚拟(内存¹。在这种情况下,在编译时知道大小并不那么重要。

另外,请记住,堆栈的大小有限,主要用于在无限递归消耗所有可用内存之前检测它们。通常限制固定在 1MB 左右,您很少达到这个限制。除非您分配大型对象,否则这些对象应放置在堆中。

至于你应该使用什么,可能是一个std::vector<int>.但这真的取决于你想做什么。

另请注意,C++11 有一个 std::array 类,必须在编译时知道其大小。C++14 应该引入 std::dynarray ,但它被推迟了,因为关于编译时未知大小的堆栈分配还有很多工作要做。


¹ 块通常出于性能原因按顺序分配,但这不是必需的。

² 如前所述,在编译时知道大小并不是一个硬性要求,但它使事情变得更简单。

在第一种情况下,您静态分配内存空间来保存整数。这是在编译程序时完成的,因此存储量不灵活。

在后一种情况下,您将动态分配内存空间来保存整数。这是在程序运行时完成的,因此所需的存储量可以灵活。

第二个调用实际上是一个与操作系统对话的函数,以便在内存中找到要使用的位置。在第一种情况下不会发生相同的过程。

int array[n]在编译时在调用堆栈上分配一个固定长度的数组,因此n需要在编译时知道(除非使用特定于编译器的扩展来允许在运行时分配,但数组仍在堆栈上(。

int *array = new int[n]运行时在堆上分配一个动态长度数组,因此n不需要在编译时知道

你的问题唯一有效的答案是,因为标准是这样说的。

与C99相比,C++从不费心指定可变长度数组(VLA(,因此获得可变大小数组的唯一方法是使用动态分配,mallocnew或其他内存管理器。

公平地说,C++,运行时大小的堆栈分配使堆栈展开稍微复杂,这也会使使用该功能的函数的异常处理更加麻烦。

无论如何,即使您的编译器提供了 C99 功能作为扩展,最好始终严格控制堆栈的使用:
没有办法从吹垮堆栈限制中恢复,并且由于某种原因,错误情况只是保留为未定义行为。

在C++中模拟VLA的最简单方法,尽管没有避免动态分配(以及突破极限的危险(的性能优势:

unique_ptr<T[]> array{new T[n]};

在表达式中

new int[n]

int[n]不是类型。C++对待"数组new"和"非数组new"不同。N3337标准草案对new有这样的说法:

当分配的对象是数组(即,使用 noptr-new-declarator 语法或 new-type-id 或 type-id 表示数组类型(时,new-expression 将生成指向数组的初始元素(如果有(的指针。

noptr-new-declarator 引用了这种特殊情况(评估n并创建此大小的数组(,请参阅:

noptr-new-declarator

[ 表达式 ] 属性-说明符-序列选项

noptr-new-declarator [ constant-expression ] attribute-specifier-seqopt

但是,您不能在"通常"声明中使用它,例如

int array[n];

或在typedef

typedef int variable_array[n];

这与 C99 VLA 不同,在 C99 VLA 中,两者都是允许的。

我应该改用向量吗?

是的,你应该。你应该一直使用向量,除非你有非常充分的理由不这样做(在过去的 7 年里,有一次我使用了new - 当我为学校作业实施 vector 时(。

不,第二个不是声明数组。 它使用数组形式 operator new ,并且特别允许第一维是可变的。

这是因为C++语言没有C99中引入的称为"可变长度数组"(VLA(的C功能。

C++在采用此 C 功能方面滞后,因为其库中的 std::vector 类型满足了大多数要求。

此外,2011年的C标准倒退了,使VLA成为可选功能。

简而言之,VLA 允许您使用运行时值来确定在自动存储中分配的本地数组的大小:

int func(int variable)
{
   long array[variable]; // VLA feature
   // loop over array
   for (size_t i = 0; i < sizeof array / sizeof array[0]; i++) {
     // the above sizeof is also a VLA feature: it yields the dynamic
     // size of the array, and so is not a compile-time constant,
     // unlike other uses of sizeof!
   } 
}

VLA早在C99之前就存在于GNU C方言中。在没有 VLA 的 C 方言中,声明中的数组维度必须是常量表达式。

即使在带有VLAC方言中,也只有某些数组可以是VLA。例如,静态数组不能,动态数组也不能(对于结构内的实例数组,即使该结构的实例是动态分配的(。

无论如何,由于您正在C++编码,因此这是没有意义的!

请注意,使用 operator new 分配的存储不是 VLA 功能。这是动态分配的特殊C++语法,它返回指针类型,如您所知:

int *p = new int[variable];

与VLA不同,这个对象将一直存在,直到它被delete []显式销毁,并且可以从周围的范围返回。

因为它

有不同的语义:

如果n是编译时常量(与您的示例不同(:

int array[n]; //valid iff n is compile-time constant, space known at compile-time

但请考虑何时n是运行时值:

int array[n]; //Cannot have a static array with a runtime value in C++
int * array = new int[n]; //This works because it happens at run-time, 
                          // not at compile-time! Different semantics, similar syntax.

在 C99 中,您可以为数组设置运行时n,并在运行时在堆栈中创建空间。C++中有一些类似的扩展建议,但还没有一个进入标准。

您可以在堆栈上静态分配内存,也可以在上动态分配内存。

在第一种情况下,您的函数包含具有可能可变长度的数组的声明,但这是不可能的,因为原始数组编译时必须具有固定大小,因为它们是在堆栈上分配的。因此,它们的大小必须指定为常量,例如 5 .你可以有这样的东西:

foo(){
    int array[5]; // raw array with fixed size 5
}

使用指针,您可以为将指向的内存指定可变大小,因为此内存将在堆上动态分配。在第二种情况下,您将使用参数 n 来指定将分配的内存空间。

最后,我们可以说指针不是数组:使用指针分配的内存在上分配,而为原始数组分配的内存在堆栈上分配。

原始数组有很好的替代品,例如标准容器向量,它基本上是一个长度大小可变的容器。

确保您很好地理解动态和静态内存分配之间的区别,堆栈上分配的内存上分配的内存之间的差异。