在过去的几年里,我不怎么使用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
}

矢量版本变得相当笨拙:

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

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

看看comp. stdc++上的讨论,很明显这个问题很有争议,争论双方都有一些重量级人物。当然,std::vector并不总是更好的解决方案。


当前回答

VLAs是可变修改类型家族的一部分。 这类类型非常特殊,因为它们有运行时组件。

代码:

int A[n];

被编译器视为:

typedef int T[n];
T A;

注意,数组的运行时大小并不与变量A绑定,而是与变量的类型绑定。

没有什么可以阻止创建这种类型的新变量:

T B,C,D;

或者指针或数组

T *p, Z[10];

此外,指针允许创建动态存储的vla。

T *p = malloc(sizeof(T));
...
free(p);

这消除了一个流行的神话,即VLAs只能在堆栈上分配。

回到刚才的问题。

这个运行时组件不能很好地与类型演绎一起工作,而类型演绎是c++类型系统的基础之一。它不可能使用模板,演绎和重载。

c++类型系统是静态的,所有类型都必须在编译时完全定义或推导。 虚拟机类型只在程序执行时完成。 在已经非常复杂的c++中引入虚拟机类型的额外复杂性被认为是不合理的。主要是因为它们主要的实际应用 是自动vla (int A[n];),它们有std::vector的替代形式。

这有点令人遗憾,因为VM类型为处理多维数组的程序提供了非常优雅和高效的解决方案。

在C语言中,你可以简单地写:

void foo(int n, int A[n][n][n]) {
  for (int i = 0; i < n; ++i)
    for (int j = 0; j < n; ++j)
      for (int k = 0; k < n; ++k)
        A[i][j][k] = i * j * k;
}

...

int A[5][5][5], B[10][10][10];
foo(5, A);
foo(10, B);

现在尝试在c++中提供高效和优雅的解决方案。

其他回答

最近在usenet上有一个关于这个问题的讨论:为什么c++ 0x中没有VLAs。

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

C99 VLAs可以在不浪费空间或调用未使用元素的构造函数的情况下创建小型数组,但它们将对类型系统带来相当大的更改(您需要能够根据运行时值指定类型-目前c++中还不存在这种功能,除了新的操作符类型说明符,但它们经过了特殊处理,因此运行时性不会逃脱新操作符的作用域)。

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

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

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

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

注意:正如在Mac OS X的alloca(3)手册中提到的,“alloca()函数依赖于机器和编译器;不鼓励使用它。”告诉你一声。

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

C99中的变长数组基本上是一个错误。为了支持VLAs, C99不得不根据常识作出以下让步:

sizeof x is no longer always a compile-time constant; the compiler must sometimes generate code to evaluate a sizeof-expression at runtime. Allowing two-dimensional VLAs (int A[x][y]) required a new syntax for declaring functions that take 2D VLAs as parameters: void foo(int n, int A[][*]). Less importantly in the C++ world, but extremely important for C's target audience of embedded-systems programmers, declaring a VLA means chomping an arbitrarily large chunk of your stack. This is a guaranteed stack-overflow and crash. (Anytime you declare int A[n], you're implicitly asserting that you have 2GB of stack to spare. After all, if you know "n is definitely less than 1000 here", then you would just declare int A[1000]. Substituting the 32-bit integer n for 1000 is an admission that you have no idea what the behavior of your program ought to be.)

好了,现在让我们开始讨论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++中真的有意义吗?

As every other answerer has already pointed out, C++ provides lots of heap-allocation mechanisms (std::unique_ptr<int[]> A = new int[n]; or std::vector<int> A(n); being the obvious ones) when you really want to convey the idea "I have no idea how much RAM I might need." And C++ provides a nifty exception-handling model for dealing with the inevitable situation that the amount of RAM you need is greater than the amount of RAM you have. But hopefully this answer gives you a good idea of why C99-style VLAs were not a good fit for C++ — and not really even a good fit for C99. ;)


有关该主题的更多信息,请参阅Bjarne Stroustrup 2013年10月关于VLAs的论文N3810“阵列扩展的替代方案”。Bjarne的POV与我的非常不同;N3810更侧重于为这些东西找到一个好的c++语法,并反对在c++中使用原始数组,而我更关注元编程和类型系统的含义。我不知道他是否认为元编程/类型系统的含义是已解决的、可解决的,还是仅仅是无趣的。


“合理使用可变长度数组”(Chris Wellons, 2019-10-27)是一篇很好的博客文章,触及了许多相同的观点。

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

顺便说一句,关于“为什么”c++标准是这样的问题,可以去Usenet新闻组comp. stdc++。

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

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

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