过去几年我很少使用C。当我读书时 这个问题 今天我遇到了一些我不熟悉的C语法。

显然在 C99 以下语法有效:

void foo(int n) {
    int values[n]; //Declare a variable length array
}

这似乎是一个非常有用的功能。是否曾经讨论过将其添加到 C++ 标准中?如果有,为什么会被省略?

一些潜在的原因:

  • 编译器供应商实施起来很困难
  • 与标准的某些其他部分不兼容
  • 功能可以用其他 C++ 结构来模拟

C++ 标准规定数组大小必须是常量表达式 (8.3.4.1)。

是的,当然我意识到在玩具示例中可以使用 std::vector<int> values(m);, ,但这会从堆而不是堆栈分配内存。如果我想要一个多维数组,例如:

void foo(int x, int y, int z) {
    int values[x][y][z]; // Declare a variable length array
}

vector 版本变得相当笨拙:

void foo(int x, int y, int z) {
    vector< vector< vector<int> > > values( /* Really painful expression here. */);
}

切片、行和列也可能分布在整个内存中。

查看讨论 comp.std.c++ 显然,这个问题颇具争议,争论双方都有一些重量级人物。这当然不明显 std::vector 始终是更好的解决方案。

有帮助吗?

解决方案

最近在 usenet 上掀起了一场关于此问题的讨论: 为什么 C++0x 中没有 VLA.

我同意那些似乎同意必须在堆栈上创建一个潜在的大型数组(通常只有很少的可用空间)的人的观点。争论的焦点是,如果您事先知道大小,则可以使用静态数组。如果您事先不知道大小,您将编写不安全的代码。

C99 VLA 可以提供一个小好处,即能够创建小型数组,而无需浪费空间或调用未使用元素的构造函数,但它们会给类型系统带来相当大的更改(您需要能够根据运行时值指定类型 - 这目前的 C++ 中尚不存在,除了 new 运算符类型说明符,但它们经过特殊处理,因此运行时性不会脱离运算符的范围 new 操作员)。

您可以使用 std::vector, ,但它并不完全相同,因为它使用动态内存,并且使其使用自己的堆栈分配器并不容易(对齐也是一个问题)。它也不能解决同样的问题,因为向量是可调整大小的容器,而 VLA 是固定大小的。这 C++ 动态数组 提案旨在引入基于库的解决方案,作为基于语言的 VLA 的替代方案。然而,据我所知,它不会成为 C++0x 的一部分。

其他提示

(背景:我有一些实现 C 和 C++ 编译器的经验。)

C99 中的可变长度数组基本上是一个失误。为了支持 VLA,C99 必须对常识做出以下让步:

  • sizeof x 不再总是编译时常量;编译器有时必须生成代码来评估 sizeof-运行时的表达式。

  • 允许二维 VLA(int A[x][y])需要一种新语法来声明采用 2D VLA 作为参数的函数: void foo(int n, int A[][*]).

  • 在 C++ 世界中不太重要,但对于 C 的嵌入式系统程序员的目标受众来说极其重要,声明 VLA 意味着咀嚼一个 任意大 你的堆栈的一部分。这是一个 保证 堆栈溢出和崩溃。(任何时候你声明 int A[n], ,您隐含地断言您有 2GB 的堆栈可用。毕竟,如果你知道“n 这里肯定小于 1000”,那么你只需声明 int A[1000]. 。替换 32 位整数 n 为了 1000 就等于承认你不知道你的程序应该有什么行为。)

好的,现在让我们开始讨论 C++。在 C++ 中,我们在“类型系统”和“值系统”之间有与 C89 相同的强烈区别……但我们确实开始以 C 所没有的方式依赖它。例如:

template<typename T> struct S { ... };
int A[n];
S<decltype(A)> s;  // equivalently, S<int[n]> s;

如果 n 不是编译时常量(即,如果 A 是可变修改类型),那么到底是什么类型 S?会 S的类型 仅在运行时确定?

那这个呢:

template<typename T> bool myfunc(T& t1, T& t2) { ... };
int A1[n1], A2[n2];
myfunc(A1, A2);

编译器必须为某些实例化生成代码 myfunc. 。该代码应该是什么样的?如果我们不知道代码的类型,我们如何静态生成该代码 A1 在编译时?

更糟糕的是,如果在运行时结果是这样怎么办? n1 != n2, , 以便 !std::is_same<decltype(A1), decltype(A2)>()?在这种情况下,调用 myfunc 甚至不应该编译, ,因为模板类型推导应该失败!我们如何才能在运行时模拟这种行为?

基本上,C++ 正朝着将越来越多的决策推向决策的方向发展。 编译时:模板代码生成, constexpr 功能评价等。与此同时,C99正忙着推传统 编译时 决定(例如 sizeof) 进入 运行. 。考虑到这一点,付出任何努力真的有意义吗? 将 C99 风格的 VLA 集成到 C++ 中?

正如其他回答者已经指出的那样,C++ 提供了许多堆分配机制(std::unique_ptr<int[]> A = new int[n]; 或者 std::vector<int> A(n); 成为显而易见的)时,当您真的想传达“我不知道我可能需要多少公羊”的想法时。 C ++提供了一个漂亮的异常处理模型,以应对您所需的RAM量的不可避免的情况大于您所需的RAM量。但希望 答案让您很好地了解为什么 C99 样式 VLA 不是 非常适合 C++,但实际上甚至不太适合 C99。;)


有关该主题的更多信息,请参阅 N3810“数组扩展的替代方案”, ,Bjarne Stroustrup 2013 年 10 月关于 VLA 的论文。Bjarne 的视角与我的非常不同;N3810更专注于寻找一个好的C++ish 句法 以及不鼓励在 C++ 中使用原始数组,而我更关注元编程和类型系统的影响。我不知道他是否认为元编程/类型系统的影响已经解决、可以解决,或者只是无趣。

如果您愿意,您始终可以使用 alloca() 在运行时在堆栈上分配内存:

void foo (int n)
{
    int *values = (int *)alloca(sizeof(int) * n);
}

在堆栈上分配意味着当堆栈展开时它将自动释放。

快速说明:正如 Mac OS X 的 alloca(3) 手册页中提到的,“alloca() 函数依赖于机器和编译器;不鼓励使用它。”正如您所知。

在我自己的工作中,我意识到每次我想要像可变长度自动数组或 alloca() 这样的东西时,我并不真正关心内存物理上位于 cpu 堆栈上,只是关心它来自一些堆栈分配器不会导致到普通堆的缓慢访问。所以我有一个每线程对象,它拥有一些内存,可以从中推送/弹出可变大小的缓冲区。在某些平台上我允许它通过 mmu 增长。其他平台具有固定大小(通常也伴随有固定大小的 cpu 堆栈,因为没有 mmu)。我使用的一个平台(手持游戏机)无论如何都拥有宝贵的少量 CPU 堆栈,因为它驻留在稀缺、快速的内存中。

我并不是说永远不需要将可变大小的缓冲区推送到 CPU 堆栈上。老实说,当我发现这不是标准时,我感到很惊讶,因为这个概念看起来确实足够适合该语言。但对我来说,“可变大小”和“必须物理上位于 CPU 堆栈上”的要求从未同时出现。这是关于速度的,所以我制作了自己的“数据缓冲区并行堆栈”。

在某些情况下,与执行的操作相比,分配堆内存的成本非常昂贵。一个例子是矩阵数学。如果您使用较小的矩阵(例如 5 到 10 个元素)并进行大量算术运算,那么 malloc 开销将非常显着。同时,将大小设置为编译时常量似乎非常浪费且不灵活。

我认为 C++ 本身是如此不安全,以至于“尽量不要添加更多不安全功能”的论点并不是很强。另一方面,由于 C++ 可以说是运行时效率最高的编程语言,因此它的特性总是非常有用:编写性能关键程序的人很大程度上会使用 C++,他们需要尽可能多的性能。将内容从堆移动到堆栈就是一种可能性。减少堆块的数量是另一个问题。允许 VLA 作为对象成员是实现此目的的一种方法。我正在研究这样的建议。诚然,实现起来有点复杂,但似乎相当可行。

似乎它将在 C++14 中可用:

https://en.wikipedia.org/wiki/C%2B%2B14#Runtime-sized_one_Dimensional_arrays

更新:它没有进入 C++14。

这被考虑包含在 C++/1x 中, 但被丢弃了 (这是对我之前所说的更正)。

无论如何,它在 C++ 中用处不大,因为我们已经有了 std::vector 来填补这个角色。

为此使用 std::vector 。例如:

std::vector<int> values;
values.resize(n);

内存将在堆上分配,但这只会带来很小的性能缺陷。此外,明智的做法是不要在堆栈上分配大数据块,因为它的大小相当有限。

C99 允许 VLA。并且它对如何声明 VLA 提出了一些限制。详见标准6.7.5.2。C++ 不允许 VLA。但 g++ 允许这样做。

像这样的数组是 C99 的一部分,但不是标准 C++ 的一部分。正如其他人所说,向量始终是一个更好的解决方案,这可能就是为什么可变大小的数组不在 C++ 标准中(或在提议的 C++0x 标准中)。

顺便说一句,对于“为什么”C++ 标准是这样的问题,请访问受主持的 Usenet 新闻组 comp.std.c++ 是该去的地方。

如果您在编译时知道该值,则可以执行以下操作:

template <int X>
void foo(void)
{
   int values[X];

}

编辑:您可以创建一个使用堆栈分配器 (alloca) 的向量,因为分配器是模板参数。

我有一个对我来说确实有效的解决方案。我不想分配内存,因为需要运行多次的例程会产生碎片。答案是极其危险的,所以使用它需要您自担风险,但它利用汇编来在堆栈上预留空间。我下面的示例使用字符数组(显然其他大小的变量需要更多内存)。

void varTest(int iSz)
{
    char *varArray;
    __asm {
        sub esp, iSz       // Create space on the stack for the variable array here
        mov varArray, esp  // save the end of it to our pointer
    }

    // Use the array called varArray here...  

    __asm {
        add esp, iSz       // Variable array is no longer accessible after this point
    } 
}

这里的危险有很多,但我将解释一些:1.将可变大小的一半更改会杀死堆栈位置2。超高数组范围将破坏其他变量和可能的代码3。这在 64 位版本中不起作用...需要不同的程序集(但宏可能会解决该问题)。4.特定于编译器(在编译器之间移动可能会遇到问题)。我没有尝试过所以我真的不知道。

在 C/C++ 中,您需要一个常量表达式来声明数组。

对于动态大小的数组,您需要在堆上分配内存,然后管理该内存的生命周期。

void foo(int n) {
    int* values = new int[n]; //Declare a variable length array
    [...]
    delete [] values;
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top