无论如何,VLA有什么意义?

我理解变长数组是什么以及它们是如何实现的。 这个问题是关于它们存在的原因。

我们知道VLA只允许在function块(或原型)中使用,并且它们基本上不能在堆栈上的任何地方(假设正常实现):C11,6.7.6.2-2:

如果标识符被声明为具有可变修改类型,则它应该是普通标识符(如6.2.3中所定义),没有链接,并且具有块范围或函数原型范围。 如果标识符被声明为具有静态或线程存储持续时间的对象,则它不应具有可变长度数组类型。

我们举一个小例子:

void f(int n) { int array[n]; /* etc */ } 

有两种情况需要注意:

  • n <= 0f必须防止这种情况,否则行为是不确定的:C11,6.7.6.2-5(强调我的):

    如果size是一个不是整数常量表达式的表达式:如果它出现在函数原型范围的声明中,则将其视为*被替换为* ; 否则, 每次评估它时,其值应大于零 。 变长数组类型的每个实例的大小在其生命周期内不会改变。 如果size表达式是sizeof运算符的操作数的一部分,并且更改size表达式的值不会影响运算符的结果,则无法指定是否计算size表达式。

  • n > stack_space_left / element_size :没有标准的方法来查找剩余多少堆栈空间(因为只要涉及标准就没有堆栈这样的东西)。 所以这个测试是不可能的。 只有明智的解决方案是为n设置一个预定义的最大可能大小,比如N ,以确保不会发生堆栈溢出。

换句话说,程序员必须确保0 < n <= N才能选择N 但是,无论如何,程序应该适用于n == N ,因此也可以使用常量大小N而不是可变长度n声明数组。

我知道引入了VLA以替换alloca (也在本回答中提到),但实际上它们是相同的(在堆栈上分配可变大小的内存)。

所以问题是为什么alloca和VLA存在,为什么他们不被弃用? 在我看来,使用VLA的唯一安全方法是使用有限大小,在这种情况下,采用具有最大大小的正常arrays始终是可行的解决方案。

看看评论和答案,在我看来,当你知道通常你的输入不是太大(类似于知道你的递归可能不是太深)时,VLA是有用的,但你实际上没有上限,你通常会忽略可能的堆栈溢出(类似于用递归忽略它们)希望它们不会发生。

它实际上也可能不是一个问题,例如,如果你有无限的堆栈大小。

也就是说,这是他们的另一个用途,我发现它实际上没有在堆栈上分配内存,但更容易使用动态多维数组。 我将通过一个简单的例子来certificate:

 #include  #include  int main(void) { size_t n, m; scanf("%zu %zu", &n, &m); int (*array)[n][m] = malloc(sizeof *array); for (size_t i = 0; i < n; ++i) for (size_t j = 0; j < m; ++j) (*array)[i][j] = i + j; free(array); return 0; } 

尽管你提到了关于VLA的所有要点,但VLA最好的部分是编译器自动处理存储管理以及边界不是编译时常量的数组的索引计算的复杂性。
如果您想要本地动态内存分配,那么唯一的选择是VLA。

我认为这可能是C99采用VLA的原因(C11上可选)。


我想澄清的一点是alloca和VLA之间存在一些显着的差异 。 这篇文章指出了不同之处:

  • 只要当前函数持续存在,内存alloca()返回就是有效的。 只要VLA的标识符保留在范围内,VLA占用的内存的生存期就是有效的。
  • 例如,你可以在循环中alloca()内存并使用循环外的内存,因为当循环终止时标识符超出范围,VLA就会消失。

你的论点似乎是因为必须绑定检查VLA的大小,为什么不分配最大大小并完成运行时分配。

这个论点忽略了这样一个事实:内存是系统中有限的资源,在许多进程之间共享。 在一个进程中浪费分配的内存对任何其他进程都不可用(或者可能是,但代价是交换到磁盘)。

通过相同的参数,当我们可以静态地分配可能需要的最大大小时,我们不需要在运行时malloc数组。 最后,堆耗尽仅略微优于堆栈溢出。