为什么不是可变长度数组是C ++标准的一部分?
在过去的几年中,我并没有使用C语言。 当我今天看到这个问题时 ,我碰到了一些我不熟悉的C语法。
显然在C99中 ,以下语法是有效的:
void foo(int n) { int values[n]; //Declare a variable length array }
这似乎是一个非常有用的function。 有没有关于将其添加到C ++标准的讨论,如果是的话,为什么它被省略?
一些潜在的原因:
- 供编译器厂商使用
- 与标准的其他部分不兼容
- function可以与其他C ++结构模拟
C ++标准规定数组大小必须是一个常量expression式(8.3.4.1)。
是的,我当然知道,在玩具的例子中,可以使用std::vector<int> values(m);
,但是这从堆中分配内存而不是堆栈。 如果我想要一个multidimensional array,如:
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
永远不是一个更好的解决scheme。
最近有一个关于usenet启动的讨论: 为什么没有在C ++ 0x的VLA 。
我同意那些似乎同意在堆栈上创build一个通常只有很less空间的潜在大arrays的人不太好。 参数是,如果你事先知道大小,你可以使用一个静态数组。 如果您事先不知道大小,您将编写不安全的代码。
C99 VLA可以提供创build小型数组的小优点,而不会浪费空间或为未使用的元素调用构造函数,但是它们会对types系统引入相当大的变化(您需要能够根据运行时值指定types – this在当前的C ++中还不存在,除了new
运算符types说明符,但是它们是专门处理的,因此运行时不会逃避new
运算符的作用域)。
你可以使用std::vector
,但它不完全相同,因为它使用dynamic内存,并且使用自己的堆栈分配器并不是很容易(alignment也是一个问题)。 它也不能解决同样的问题,因为vector是一个可resize的容器,而VLA是固定大小的。 C ++dynamic数组提议旨在引入基于库的解决scheme,作为基于语言的VLA的替代scheme。 但是,就我所知,它不会成为C ++ 0x的一部分。
(背景:我有一些实现C和C ++编译器的经验。)
C99中的变长数组基本上是一个失误。 为了支持VLA,C99必须做出以下常识的让步:
-
sizeof x
不再总是编译时常量; 编译器有时必须生成代码来在运行时评估sizeof
expression。 -
允许二维的VLA(
int A[x][y]
)需要一个新的语法来声明以二维VLA为参数的函数:void foo(int n, int A[][*])
。 -
在C ++领域不太重要,但是对于embedded式系统程序员的目标读者来说非常重要,声明一个VLA就意味着你的堆栈中的任意大块。 这是一个保证堆栈溢出和崩溃。 (任何时候你声明
int A[n]
,你都暗示你有2GB的堆栈空间,毕竟,如果你知道“n
在这里肯定小于1000”,那么你只需声明int A[1000]
用32位整数n
代替1000
表示你不知道程序的行为应该是什么。)
好的,现在我们来谈谈C ++。 在C ++中,我们与C89所做的“types系统”和“价值系统”有着同样的强烈区别……但是我们真的开始以C没有的方式依赖它。 例如:
template<typename T> struct S { ... }; int A[n]; S<decltype(A)> s; // equivalently, S<int[n]> s;
如果n
不是一个编译时常量(即,如果A
是可变修改types),那么究竟是什么types的S
? S
的types也只能在运行时确定吗?
那这个呢:
template<typename T> bool myfunc(T& t1, T& t2) { ... }; int A1[n1], A2[n2]; myfunc(A1, A2);
编译器必须为myfunc
一些实例化生成代码。 该代码应该是什么样子? 如果我们不知道编译时A1
的types,我们如何静态生成这些代码呢?
更糟糕的是,如果在运行时发现n1 != n2
,那么!std::is_same<decltype(A1), decltype(A2)>()
? 在这种情况下,调用myfunc
甚至不应该编译 ,因为模板types的扣除应该失败! 我们怎样才能在运行时模拟这种行为?
基本上,C ++正朝着将更多决策推向编译时间的方向发展:模板代码生成, constexpr
函数评估等等。 与此同时,C99忙于将传统的编译时决策(例如sizeof
)推向运行时 。 考虑到这一点, 试图将C99风格的VLA集成到C ++中,是否真的有意义?
正如其他答案已经指出的那样,C ++提供了许多堆分配机制( std::unique_ptr<int[]> A = new int[n];
或者std::vector<int> A(n);
明显的),当你真的想传达这个想法“我不知道我可能需要多lessRAM”。 而且C ++提供了一个漂亮的exception处理模型来处理你所需要的RAM数量大于你拥有的RAM数量的不可避免的情况。 但是希望这个答案能给你提供一个很好的概念,说明为什么C99风格的VLA 不适合C ++,而且不适合C99。 ;)
有关该主题的更多信息,请参阅N3810“arrays扩展的替代scheme” ,Bjarne Stroustrup 2013年10月关于VLA的文章。 Bjarne的POV与我的非常不同, N3810更注重为事物find一个好的C ++ ish 语法 ,并且不鼓励在C ++中使用原始数组,而我更多地关注元编程和types系统的含义。 我不知道他是否认为元编程/types系统的含义可以解决,可以解决,或者仅仅是无趣。
如果你希望的话,你总是可以在运行时使用alloca()来分配堆栈中的内存。
void foo (int n) { int *values = (int *)alloca(sizeof(int) * n); }
被分配在堆栈上意味着当堆栈展开时它将被自动释放。
快速提示:正如在Mac OS X手册页中提到的alloca(3)所述,“alloca()函数是机器和编译器相关的,它的使用是被禁止的。 就这样你知道。
与所执行的操作相比,在某些情况下分配堆内存是非常昂贵的。 matrixmath就是一个例子。 如果你使用小的matrix表示5到10个元素,并做了大量的算术,malloc的开销将是非常重要的。 同时使编译时间不变,看起来非常浪费和不灵活。
我认为C ++本身是非常不安全的,所以“试图不添加更多不安全的function”的论据不是很强。 另一方面,C ++可以说是最具运行时间效率的编程语言特性,它使得它更加有用:编写性能关键型程序的人将在很大程度上使用C ++,而且他们需要尽可能多的性能。 把东西从堆栈移到堆栈就是这样一种可能性。 减less堆块的数量是另一个。 允许VLA作为对象成员可以实现这一点。 我正在研究这样的build议。 诚然,实施起来有点复杂,但似乎相当可行。
在我自己的工作中,我已经意识到,每当我想要像变长度自动数组或alloca()这样的东西时,我并不在乎内存是否在物理上位于CPU堆栈上,只是它来自一些堆栈分配器,没有招致缓慢旅行到一般堆。 所以我有一个每线程对象,拥有一些内存,它可以推/可变大小的缓冲区。 在一些平台上,我允许这通过mmu增长。 其他平台有一个固定的大小(通常伴随着固定大小的CPU堆栈,因为没有mmu)。 我使用的一个平台(掌上游戏机)拥有宝贵的小CPU堆栈,因为它驻留在稀缺,快速的内存中。
我并不是说将可变大小的缓冲区推入CPU堆栈是不需要的。 老实说,当我发现这不是标准的时候,我很惊讶,因为它似乎很适合这个语言。 对我来说,“可变大小”和“必须在物理上位于CPU堆栈”的要求从来没有出现在一起。 这是关于速度,所以我做了我自己的“数据缓冲区并行堆栈”。
这被认为是包含在C ++ / 1x中, 但被放弃了 (这是对我之前所说的修正)。
因为我们已经有了std::vector
来填充这个angular色,所以它在C ++中的用处不大。
似乎它将在C ++ 14中可用:
https://en.wikipedia.org/wiki/C%2B%2B14#Runtime-sized_one_dimensional_arrays
更新:它没有把它变成C ++ 14。
如果你知道编译时的值,你可以这样做:
template <int X> void foo(void) { int values[X]; }
编辑:你可以创build一个使用堆栈分配器(alloca)的向量,因为分配器是一个模板参数。
为此使用std :: vector。 例如:
std::vector<int> values; values.resize(n);
内存将被分配在堆上,但这只有一个小的性能缺陷。 此外,明智的做法是不要在堆栈上分配大型数据块,因为它的大小相当有限。
C99允许VLA。 而且对如何申报VLA也有一些限制。 有关详细信息,请参阅标准的6.7.5.2。 C ++不允许VLA。 但是g ++允许它。
你需要一个常量expression式来在C / C ++中声明一个数组。
对于dynamic大小的数组,你需要在堆上分配内存,然后pipe理这个内存的使用寿命。
void foo(int n) { int values = new int[n]; //Declare a variable length array [...] delete [] values; }
这样的数组是C99的一部分,但不是标准C ++的一部分。 正如其他人所说,vector总是一个更好的解决scheme,这可能是为什么可变大小的数组不是在C ++标准(或build议的C ++ 0x标准)。
顺便说一下,对于“为什么”C ++标准是这样的问题的问题,适度的Usenet新闻组comp.std.c ++是去的地方。
我有一个解决scheme,实际上为我工作。 我不想分配内存,因为需要多次运行的例程上的碎片。 答案是非常危险的,所以使用它是需要您自担风险的,但它会利用组件来预留堆栈空间。 我下面的例子使用了一个字符数组(显然其他大小的variables会需要更多的内存)。
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.更改variables大小的一半将杀死堆栈位置2.超越数组边界将破坏其他variables和可能的代码3.这不工作在一个64位build立…需要不同的程序集(但macros可能会解决这个问题)。 4.编译器特定(可能在编译器之间移动有困难)。 我没有试过,所以我真的不知道。