【问题标题】:Why no variable size array in stack?为什么堆栈中没有可变大小的数组?
【发布时间】:2011-12-10 09:14:54
【问题描述】:

我真的不明白为什么我不能在堆栈上有一个可变大小的数组,所以像

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

据我了解,部分数据段的堆栈(-段)因此不是“恒定大小”。

【问题讨论】:

  • 我认为没有任何技术原因,因为 C99 添加了该功能。
  • @MarkRansom:大多数情况下,这很棘手,我们已经有了std::vector,所以他们不需要编译器来做。另一方面,gcc 和 msvc 无论如何都可以做到。
  • 可能是因为过去应用程序专用于堆栈的内存较少。堆栈也是一个包含局部变量、参数值和返回地址的固定结构。对于具有固定大小堆栈的平台,此功能可能很危险。
  • @ThomasMatthews:这种解释在任何支持递归的语言中都站不住脚。
  • @ThomasMatthews,危险从未阻止将功能添加到 C++ 中。您应该知道自己在做什么。

标签: c++ arrays dynamic-data


【解决方案1】:

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

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


有一个 proposal 用于在 C++11 中引入可变长度数组,但最终被放弃了,因为它需要对 C++ 中的类型系统进行大量更改。能够在堆栈上创建小数组而不浪费空间或为未使用的元素调用构造函数的好处被认为对于 C++ 类型系统的大更改来说不够重要。

【讨论】:

  • 那个,alloca 可以和placement new[]一起使用。虽然在多维情况下会变得相当混乱。
【解决方案2】:

我将尝试用一个例子来解释这一点:

假设你有这个功能:

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++ 不会即时设置位置,它只会在编译程序时完成一次。为什么?这很简单:因为实际上不需要将可变大小的对象放入堆栈,而且在分配/重新分配堆栈空间时,拥有它们会减慢所有程序的速度。

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

【讨论】:

  • 你要做什么delete arr;是什么意思?谁曾说过这是有效的? arr 不是指针。
  • arr 不是指针,但这将是唯一(当前已知的)取消分配它的方法。这只是解释其工作原理的一种理论方法。
  • 我们谈论的是 C99 VLA,它们定义明确;不需要假设。 arr 是自动分配的。
  • 绝对不是。我们正在讨论为什么这些没有作为标准实施。无论如何,问题中甚至没有提到 C99 VLA。
【解决方案3】:

请注意,该提案已被拒绝,以下内容不再适用。不过,它可能会在未来的 C++ 版本中恢复。

N3639 中描述的 VLA 已在 Bristol 会议上被接受,并将成为 C++14 的一部分,以及库对应部分“dynarray”。因此,使用支持 C++14 的编译器,我们可以开始编写如下代码:

void func(int n)
{
    int arr[n];
}

或者使用 dynarray:

#include <dynarray>

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

【讨论】:

  • 注意:它后来不被接受,也没有进入 C++14。
  • 请删除这个答案,它(最终)不正确。
【解决方案4】:

简单的答案:因为它没有在 C++ 标准中定义。

不是那么简单的答案:因为在这种情况下,没有人提出 C++ 行为一致的东西。从标准 POV 来看,没有堆栈,它可以完全不同地实现。 C99 有 VLA,但它们似乎实现起来非常复杂,以至于 gcc 仅在 4.6 中完成了实现。我认为没有多少人愿意为 C++ 提出一些建议,并且看到编译器制造商多年来一直在为之苦苦挣扎。

【讨论】:

  • GCC 和 MSVC 都实现了它,但你说得对,我听到了两者的抱怨。
  • @MooingDuck:gcc.gnu.org/gcc-4.4/c99status.html 似乎表明这对于 C99 来说已经不容易了。对于像 dtors 和异常之类的东西,我相信这对 C++ 来说是相当困难的。如果它成为标准,更多的人将不得不实施它。我不认为很多人想要第二个export。恕我直言,未来有许多更重要的事情需要改变,而不是 VLA,这是一个不错的功能,但并非必不可少。
【解决方案5】:

堆栈相当小,而且它们的大小可能因架构而异。问题是“过度分配”并导致段错误或覆盖其他人拥有的内存是相当容易的。同时,问题的解决方案(例如vector)已经存在很长时间了。

FWIW,我读到 Stroustrup 说他不想要他们,但我不知道是在哪个采访中。

【讨论】:

    【解决方案6】:

    因为在 C++ 中静态数组需要一个静态常量大小,所以语言不允许。请注意,C99 确实支持堆栈上的 vararray,一些实现在 C++ 下支持它以及扩展。

    【讨论】:

    • 对,问题是为什么在 C++ 中会这样。他们为什么不按照 C99 的思路添加呢?
    • 我认为你在正确的轨道上。我无法证明这一点,但我相信,出于各种原因,C++ 中的 all 对象需要一个恒定的大小。为什么呢?
    • @BenjaminLindley:常量还是非零大小?你把这两者混淆了吗?
    • @Als:常数。我认为这是因为我不知道如果将 VLA 传递给以下函数会发生什么:template&lt;int N&gt; void foo(int (&amp;arr)[N])
    • @BenjaminLindley:啊,好吧,如果我们能在标准中找到更多关于它的上下文,那会很有趣。
    【解决方案7】:

    因为语言规范是这样说的。其他都不重要(由于不同的原因,用分段来解释是非常错误的)。

    【讨论】:

    • @user695652 对于初学者,因为 C++ 中没有段。
    • 我发现这个答案在智力上很懒惰。这不像标准只是从无到有,人们致力于它。为什么那些人不走C99的VLA路线?
    • @GMan:嗯,在 C++98 中遵循 C99 VLA 的路线似乎并不容易,因为 98
    • @GMan 这不是懒惰。这是教条主义。不一样!
    • @PlasmaHH:我的意思是有一个 03 标准以及新的 11 标准。
    猜你喜欢
    • 2011-03-22
    • 2023-04-01
    • 2011-10-02
    • 2021-03-15
    • 2019-04-13
    • 2022-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多