我知道C99之前的C标准(以及C ++)说堆栈上的数组大小必须在编译时知道。但那是为什么呢?堆栈上的数组在运行时分配。那么为什么大小在编译时很重要?希望有人向我解释编译器在编译时将如何处理大小。谢谢。
这种数组的例子是:
void func()
{
/*Here "array" is a local variable on stack, its space is allocated
*at run-time. Why does the compiler need know its size at compile-time?
*/
int array[10];
}
答案 0 :(得分:69)
要理解为什么可变大小的数组实现起来更复杂,您需要了解一下通常如何实现自动存储持续时间(“本地”)变量。
局部变量往往存储在运行时堆栈中。堆栈基本上是一个大的内存数组,它按顺序分配给局部变量,并且单个索引指向当前的“高水位线”。该索引是堆栈指针。
当输入一个函数时,堆栈指针向一个方向移动,为堆栈分配局部变量的内存;当函数退出时,堆栈指针向另一个方向移回,以释放它们。
这意味着仅在参考函数入口 1 处的堆栈指针的值时定义内存中局部变量的实际位置。函数中的代码必须通过堆栈指针的偏移量访问局部变量。要使用的确切偏移量取决于局部变量的大小。
现在,当所有局部变量的大小在编译时都是固定的时,这些来自堆栈指针的偏移量也是固定的 - 因此它们可以直接编码到编译器发出的指令中。例如,在此函数中:
void foo(void)
{
int a;
char b[10];
int c;
a
可能会被STACK_POINTER + 0
访问,b
可能会被STACK_POINTER + 4
访问,c
可能会被STACK_POINTER + 14
访问。< / p>
但是,当您引入一个可变大小的数组时,这些偏移量不能再在编译时计算出来;其中一些将根据数组调用函数的大小而有所不同。这使编译器编写者的事情变得更加复杂,因为他们现在必须编写访问STACK_POINTER + N
的代码 - 并且由于N
本身不同,所以它也必须存储在某处。通常这意味着进行两次访问 - 一次到STACK_POINTER + <constant>
加载N
,然后另一次加载或存储感兴趣的实际局部变量。
1。实际上,“函数入口处的堆栈指针的值”是一个有用的值,它有自己的名称 - 帧指针 - 并且许多CPU提供单独的寄存器专用于存储帧指针。在实践中,通常是帧指针,从中计算局部变量的位置,而不是堆栈指针本身。
答案 1 :(得分:6)
支持并不是一件非常复杂的事情,所以C89不允许这样做的原因是不因为当时不可能。
然而,有两个重要原因导致它不在C89中:
从历史上看,C编译器应该(相对)易于编写非常重要。此外,应该可以使编译器简单和小到足以在适度的计算机系统上运行(按80s标准)。 C的另一个重要特性是生成的代码应该始终非常高效,没有任何意外,
我认为很遗憾这些价值不再适用于C99。
答案 2 :(得分:5)
编译器必须生成代码,以便为堆栈上的帧创建空间以容纳数组和其他本地局部变量。为此,它需要数组的大小。
答案 3 :(得分:3)
取决于你如何分配数组。
如果将其创建为局部变量并指定长度,那么它很重要,因为编译器需要知道在堆栈上为数组元素分配多少空间。如果没有指定数组的大小,那么它不知道为数组元素留出多少空间。
如果只创建一个指向数组的指针,那么您需要做的就是为指针本身分配空间,然后您可以在运行时动态创建数组元素。但是在这种数组创建形式中,你要为堆中的数组元素分配空间,而不是在堆栈上。
答案 4 :(得分:3)
假设您在堆栈上创建一个可变大小的数组。在编译时将不知道函数所需的堆栈帧的大小。因此,C假设某些运行时环境要求事先知道这一点。因此限制。 C可以追溯到1970年代早期。当时许多语言都具有“静态”外观(如Fortran)
答案 5 :(得分:2)
在C ++中,这变得更加难以实现,因为存储在堆栈中的变量必须在异常事件中调用它们的析构函数,或者从给定函数或作用域返回时调用它们的析构函数。跟踪要销毁的变量的确切数量/大小会增加额外的开销和复杂性。在C语言中,可以使用类似帧指针的东西来释放隐含的VLA,在C ++中对你没有帮助,因为需要调用那些析构函数。
此外,VLA可能导致拒绝服务安全漏洞。如果用户能够提供最终用作VLA大小的任何值,那么他们可以使用足够大的值来导致进程中的堆栈溢出(因此失败)。
最后,C ++已经有一个安全有效的可变长度数组(std::vector<t>
),因此没有理由为C ++代码实现此功能。