以下代码:
foo(int n){
int array[n];
}
我知道这是无效的语法,并且它是无效的,因为c ++标准要求在编译时设置数组大小(尽管一些编译器支持以下语法)。
但是我也理解以下是有效的语法:
bar(int n){
int *array = new int[n];
}
我不明白为什么允许这样做,这与创建一个在运行时确定大小的数组相同吗?这样做是好的做法,还是我应该使用向量代替?
答案 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),因此获得可变大小数组的唯一方法是使用动态分配,malloc
,new
或其他一些内存-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 []
明确销毁该对象,并且可以从周围范围返回。