为什么变长数组不是C ++标准的一部分?

时间:2009-12-11 10:15:41

标签: c++ arrays standards variable-length-array variable-length

在过去的几年里,我并没有非常使用过C语言。当我今天阅读this question时,我遇到了一些我不熟悉的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始终是一个更好的解决方案当然不是很明显。

13 个答案:

答案 0 :(得分:190)

(背景:我在实现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”,那么你只需声明{ {1}}。将{32}整数int A[1000]替换为n是承认你不知道你的程序的行为应该是什么。)

好的,现在让我们谈谈C ++。在C ++中,我们在C89所做的“类型系统”和“价值系统”之间有着相同的强烈区别......但我们真的开始以C没有的方式依赖它。例如:

1000

如果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)>() 的调用甚至不应该编译,因为模板类型扣除应该失败!我们怎么可能在运行时模拟这种行为?

基本上,C ++正朝着将越来越多的决策推向编译时:模板代码生成,myfunc函数评估等方向发展。与此同时,C99正忙于将传统的编译时决策(例如constexpr)推送到运行时。考虑到这一点,花费任何努力尝试将C99风格的VLA集成到C ++中是否真的有意义?

正如其他每个回答者已经指出的那样,C ++提供了许多堆分配机制(sizeofstd::unique_ptr<int[]> A = new int[n];是显而易见的)当你真的想传达这个想法时“我不知道我可能需要多少RAM。“ C ++提供了一个漂亮的异常处理模型,用于处理不可避免的情况,即您需要的RAM量大于您拥有的RAM量。但希望这个的答案可以让你清楚地知道为什么C99风格的VLA 非常适合C ++ - 而且实际上并不适合C99。 ;)


有关该主题的更多信息,请参阅N3810 "Alternatives for Array Extensions",Bjarne Stroustrup 2013年10月关于VLA的论文。 Bjarne的POV与我的非常不同; N3810更侧重于为事物找到一个好的C ++ ish 语法,并且不鼓励在C ++中使用原始数组,而我更关注元编程和类型系统的含义。我不知道他是否认为元编程/类型系统的含义已经解决,可解决或仅仅是无趣的。

答案 1 :(得分:178)

最近在usenet上开始讨论这个问题:Why no VLAs in C++0x

我同意那些似乎同意必须在堆栈上创建潜在大型数组的人,这通常只有很少的可用空间,并不好。参数是,如果您事先知道大小,则可以使用静态数组。如果您事先不知道大小,则会编写不安全的代码。

C99 VLA可以提供一个很小的好处,即能够创建小型数组而不浪费空间或为未使用的元素调用构造函数,但它们会对类型系统引入相当大的更改(您需要能够根据运行时指定类型)值 - 除了new运算符类型说明符之外,这在当前C ++中尚不存在,但它们是专门处理的,因此运行时不会逃避new运算符的范围)

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

答案 2 :(得分:24)

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

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

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

快速注意:正如在用于alloca(3)的Mac OS X手册页中所提到的,“alloca()函数依赖于机器和编译器;它的使用是不受欢迎的。”你知道吗。

答案 3 :(得分:10)

在某些情况下,与执行的操作相比,分配堆内存非常昂贵。一个例子是矩阵数学。如果你使用小型矩阵说5到10个元素并做很多算术,那么malloc开销将非常重要。同时使大小成为编译时间常数似乎非常浪费和不灵活。

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

答案 4 :(得分:10)

在我自己的工作中,我意识到每次我想要可变长度自动数组或alloca()之类的东西时,我并不在乎内存实际位于cpu堆栈上,只是它来自一些堆栈分配器,它不会导致慢速跳转到一般堆。所以我有一个每线程对象拥有一些内存,可以从中推送/弹出可变大小的缓冲区。在某些平台上,我允许通过mmu增长。其他平台具有固定大小(通常伴随着固定大小的CPU堆栈,因为没有mmu)。我使用的一个平台(掌上游戏机)无论如何都拥有宝贵的小CPU堆栈,因为它存在于稀缺,快速的内存中。

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

答案 5 :(得分:9)

似乎它将在C ++ 14中提供:

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

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

答案 6 :(得分:6)

考虑将其包含在C ++ / 1x中,but was dropped(这是对我之前所说的更正)。

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

答案 7 :(得分:2)

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

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

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

答案 8 :(得分:1)

C99允许VLA。它对如何声明VLA提出了一些限制。有关详细信息,请参阅标准的6.7.5.2。 C ++不允许使用VLA。但是g ++允许它。

答案 9 :(得分:0)

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

BTW,关于“为什么”C ++标准就是这样的问题,主持的Usenet新闻组comp.std.c++是可以去的地方。

答案 10 :(得分:-1)

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

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

}

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

答案 11 :(得分:-4)

我有一个实际上适合我的解决方案。我不想分配内存,因为需要多次运行的例程碎片化。答案非常危险,因此使用它需要您自担风险,但它利用程序集来保留堆栈上的空间。我下面的例子使用了一个字符数组(显然其他大小的变量需要更多的内存)。

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.在中途改变可变大小会杀死堆栈位置 超越数组边界会破坏其他变量和可能的代码 3.这在64位构建中不起作用...需要不同的程序集(但宏可能会解决该问题)。 4.编译器特定(可能在编译器之间移动有困难)。我没试过,所以我真的不知道。

答案 12 :(得分:-6)

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

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

void foo(int n) {
    int* values = new int[n]; //Declare a variable length array
    [...]
    delete [] values;
}