当int array [n]不是时,为什么new int [n]有效?

时间:2014-12-16 00:49:31

标签: c++ arrays

以下代码:

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

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

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

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

我不明白为什么允许这样做,这与创建一个在运行时确定大小的数组相同吗?这样做是好的做法,还是我应该使用向量代替?

9 个答案:

答案 0 :(得分:26)

那是因为前者是在堆栈上分配而后者在堆上。

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

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

另外,请记住堆栈的大小有限,主要是为了在消耗所有可用内存之前检测无限递归。通常,限制大约固定在1MB左右,而您很少达到此限制。除非你分配大对象,否则应放在堆中。

截至您应该使用的内容,可能是std::vector<int>。但这实际上取决于你想要做什么。

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


由于性能原因,通常会按顺序分配

块,但这不是必需的。

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

答案 1 :(得分:8)

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

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

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

答案 2 :(得分:5)

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

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

答案 3 :(得分:5)

您问题的唯一有效答案是因为标准是这样

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

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

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

在C ++中模拟VLA的最简单方法,虽然没有避免动态分配的性能优势(以及超出限制的危险):

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

答案 4 :(得分:4)

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

答案 5 :(得分:4)

因为它有不同的语义:

如果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 ++中类似扩展的建议,但它们都没有纳入标准。

答案 6 :(得分:4)

在表达式

new int[n]

int[n]不是那种类型。 C ++将“new与数组”和“new与非数组”区别对待。 N3337标准草案可以说new

  

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

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

  

noptr-新声明符

     

[表达式] attribute-specifier-seq opt

     

noptr-new-declarator [ constant-expression ] attribute-specifier-seq opt

但是你不能在“通常”的声明中使用它,比如

int array[n];

typedef

typedef int variable_array[n];

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

  

我应该使用向量吗?

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

答案 7 :(得分:3)

您可以在 stack 上静态分配内存,也可以在 heap 上动态分配内存。

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

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

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

总结,我们可以说指针不是数组:使用指针分配的内存分配在上,而分配给原始数组的内存是分配的在堆栈上

原始数组有很好的替代方法,例如标准容器 vector ,它基本上是一个长度可变的容器。

确保您理解动态和静态内存分配之间的区别,堆栈上分配的内存与上分配的内存之间的difference

答案 8 :(得分:3)

这是因为C ++语言没有C99中引入的C功能,称为&#34;可变长度数组&#34; (VLA)。

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!
   } 
}
早在C99之前,VLA就存在于GNU C方言中。在没有VLA的C的方言中,声明中的数组维度必须是常量表达式。

即使在带有 VLA的C 的方言中,也只有某些阵列可以是VLA&#39。例如静态数组不能,动态数组也不能(例如结构中的数组,即使动态分配了该结构的实例)。

无论如何,既然你用C ++进行编码,这是没有意义的!

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

int *p = new int[variable];

与VLA不同,此对象将持续存在,直到使用delete []明确销毁该对象,并且可以从周围范围返回。