在过去的几年里,我不怎么使用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并不总是更好的解决方案。


当前回答

(背景:我有一些实现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)是一篇很好的博客文章,触及了许多相同的观点。

其他回答

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

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

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

似乎c++ 14中也可以使用:

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

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

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

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

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

因为我们已经有了std::vector来填充这个角色,所以它在c++中就没有那么有用了。

在某些情况下,与所执行的操作相比,分配堆内存的开销非常大。矩阵数学就是一个例子。如果你处理较小的矩阵,比如5到10个元素,并做大量的算术运算,malloc开销将非常大。同时,将大小设置为编译时常量似乎非常浪费且不灵活。

I think that C++ is so unsafe in itself that the argument to "try to not add more unsafe features" is not very strong. On the other hand, as C++ is arguably the most runtime efficient programming language features which makes it more so are always useful: People who write performance critical programs will to a large extent use C++, and they need as much performance as possible. Moving stuff from heap to stack is one such possibility. Reducing the number of heap blocks is another. Allowing VLAs as object members would one way to achieve this. I'm working on such a suggestion. It is a bit complicated to implement, admittedly, but it seems quite doable.