为什么栈中没有可变大小的数组?

25

我不太明白为什么不能在堆栈上使用可变大小数组,例如:

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

据我所知,栈(段)是数据段的一部分,因此其大小不是“固定的”。


6
我认为没有任何技术上的原因,因为C99已经添加了这个功能。 - Mark Ransom
3
@MarkRansom:大多数情况下,这很棘手,而且我们已经有了std::vector,所以它们不需要编译器来执行。另一方面,gcc和msvc都可以执行它。 - Mooing Duck
可能是因为历史上应用程序分配给堆栈的内存较少。此外,堆栈是一个固定结构,包含局部变量、参数值和返回地址。对于具有固定大小堆栈的平台,这个特性可能是危险的。 - Thomas Matthews
@ThomasMatthews:在任何支持递归的语言中,那个解释都站不住脚。 - Mooing Duck
2
@ThomasMatthews,危险从未阻止过将功能添加到C++。你应该知道自己在做什么。 - Mark Ransom
显示剩余2条评论
7个回答

27

可变长度数组(VLA)在C ++标准中不被允许使用。
包括gcc在内的许多编译器支持它们作为编译器扩展,但需要注意的是,使用此类扩展的任何代码都是不可移植的。

C ++提供了 std :: vector 以实现与 VLA 类似的功能。


曾经有一个提案试图引入可变长度数组到C ++11中,但最终因为需要对C ++类型系统进行大量更改而被放弃。能够在堆栈上创建小型数组而不浪费空间或调用未使用元素的构造函数的好处被认为不足以证明必须对C ++类型系统进行大规模更改。


1
那么,alloca可以与placement new[]一起使用。虽然在多维情况下会变得非常混乱。 - moshbear

6
我会尽力用一个例子来解释这个问题:
假设你有以下这个函数:
int myFunc() {
   int n = 16;
   int arr[n];
   int k = 1;
}

程序运行时,它会将这些变量按照以下方式设置到堆栈中:
- n @relative addr 0
- arr[16] @relative addr 4
- k @relative addr 64
TOTAL SIZE: 68 bytes

假设我想将arr调整为4个元素。我需要执行以下操作:

delete arr;
arr = new int[4];

现在,如果我按这种方式离开堆栈,堆栈将会有未使用空间的洞。因此,最明智的做法是将所有变量从一个位置移动到另一个位置,并重新计算它们的位置。但我们还缺少一些东西:C++不会即时设置位置,只有在编译程序时才会完成。为什么?很简单:因为没有必要将可变大小的对象放到堆栈上,而且如果这样做会减慢分配/重新分配堆栈空间时所有程序的速度。

这不是唯一的问题,还有另一个更大的问题: 当您分配数组时,您决定它将占用多少空间,编译器可以警告您是否超出了可用空间,但如果您让程序在堆栈上分配可变大小的数组,则会打开安全漏洞,因为您使得使用这种方法的所有程序都容易受到堆栈溢出的攻击。


1
你是说你要执行 delete arr;?谁说这样是合法的?arr不是一个指针。 - GManNickG
arr不是指针,但这可能是(目前已知的)唯一取消分配它的方法。这只是一种理论上的方式来解释它如何工作。 - moongoal
我们正在讨论C99 VLAs,它们是明确定义的;不需要假设情况。arr会自动分配内存。 - GManNickG
2
绝对不是。我们正在讨论为什么它们没有作为标准实现。而且,无论如何,C99 VLAs在问题中甚至都没有被提到。 - moongoal

4
请注意,该提议已经被拒绝,以下内容已不再适用。但未来版本的C++可能会重新引入该提议。
在布里斯托尔会议上,像N3639中描述的VLA已经被接受并将成为C++14的一部分,同时也有一个库对应项“dynarray”。因此,使用支持C++14的编译器,我们可以开始写出类似下面的代码:
void func(int n)
{
    int arr[n];
}

或者使用dynarray:

#include <dynarray>

void func(int n)
{
    std::dynarray<int> arr(n);
}

5
注:后来该功能被取消,并未列入C++14标准。 - M.M
4
请删除此回答,它(最终)是不正确的。 - einpoklum

3
简单回答:因为它在C++标准中没有被定义。
不那么简单的答案:因为没有人提出在这种情况下对于C++行为一致的东西。从标准的角度来看,栈并不存在,它可以完全不同地实现。C99有可变长度数组(VLAs),但它们似乎很难实现, 所以gcc直到4.6版本才完成了实现。我认为很少有人会希望为C++提出某些东西,并看到编译器制造商多年的努力。

GCC和MSVC都实现了它,但你说得对,我听到过两者的抱怨。 - Mooing Duck
1
@MooingDuck:http://gcc.gnu.org/gcc-4.4/c99status.html似乎表明即使对于C99来说这也不容易。考虑到像dtors和exceptions之类的东西,我认为这对于C ++而言相当困难。如果它成为标准,那么必须有更多的人实现它。我认为没有多少人想要第二个“export”。在我看来,未来需要改变的重要事情比VLA更多,尽管VLA是一个不错的功能,但不是必不可少的。 - PlasmaHH

1

堆栈相对较小,其大小可能会因体系结构而大相径庭。问题在于很容易“过度分配”,导致段错误或写入其他人拥有的内存。与此同时,解决该问题的方案(如vector)已经存在很长时间。

顺便说一下,我读到过 Stroustrup 说他并不希望它们存在,但我不知道是在哪次采访中。


0
因为在C ++中,静态数组需要静态常量大小,所以该语言不允许使用。请注意,C99支持堆栈上的变长数组,并且某些实现还支持它作为扩展在C ++下使用。

1
对的,问题是为什么C++中这样做,为什么他们没有遵循C99的想法并将它们添加进去? - GManNickG
我认为你在这方面走在了正确的轨道上。虽然我无法证明,但我相信由于各种原因,C++中的所有对象都需要一个常量大小。但是为什么呢? - Benjamin Lindley
@BenjaminLindley:常量或非零大小?你是混淆了两者吗? - Alok Save
@Als:常量。我认为这是其中一个原因,是因为如果将VLA传递给以下函数,我不知道会有什么预期结果:template<int N> void foo(int (&arr)[N]) - Benjamin Lindley
@BenjaminLindley:啊,好的,如果我们能在标准中找到更多相关信息那将会很有趣。 - Alok Save

-1

因为语言规范是这样规定的。其他什么都不重要(并且用段落进行解释是错的,原因有很多)。


@user695652 首先,因为C++中没有段落。 - R. Martinho Fernandes
8
我认为这个回答有些草率。标准并不是从无到有地产生的,它背后有人们的努力工作。为什么那些人没有遵循 C99 可变长度数组的路线呢? - GManNickG
@GMan:在C++98中遵循C99 VLAs的路线似乎不容易,因为98 < 99 ;) 我认为对于感兴趣的人来说,阅读C++98标准化过程中的所有邮件和提案会很有趣;我不确定,但我认为人们提出了一些东西,可以搜索“dynarray”尝试一下。但是不要相信我的话,我的记忆力不太好,你知道的... - PlasmaHH
@GMan 这不是懒惰主义,而是教条主义。两者是不同的! - xanatos
@PlasmaHH:我指的是03标准以及新的11标准。 - GManNickG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接