28

GCC 如何实现变长数组(VLA)?这样的数组是否本质上是指向动态分配存储的指针,例如由 alloca 返回的?

我能想到的另一种选择是,将这样的数组分配为函数中的最后一个变量,以便在编译时知道变量的偏移量。但是,第二个 VLA 的偏移量将在编译时再次未知。

4

2 回答 2

10

以下是从一些 GCC 文档中获取的 VLA 支持示例行的分配代码(x86 - x64 代码类似) :

char str[strlen (s1) + strlen (s2) + 1];

计算在哪里strlen (s1) + strlen (s2) + 1eaxGCC MinGW 4.8.1 - 没有优化):

mov edx, eax
sub edx, 1
mov DWORD PTR [ebp-12], edx
mov edx, 16
sub edx, 1
add eax, edx
mov ecx, 16
mov edx, 0
div ecx
imul    eax, eax, 16
call    ___chkstk_ms
sub esp, eax
lea eax, [esp+8]
add eax, 0
mov DWORD PTR [ebp-16], eax

所以它看起来本质上是alloca().

于 2014-01-17T09:49:01.383 回答
9

好吧,根据 VLA 的限制,这些只是黑暗中的一些野刺,但无论如何:

VLA 不能是:

  • 外部
  • 结构成员
  • 静止的
  • 用未指定的边界声明(函数原型除外)

所有这些都表明 VLA 被分配在堆栈上,而不是堆上。所以是的,VLA 可能是分配新块时分配的最后一块堆栈内存(块在块范围内,这些是循环、函数、分支或其他)。
这也是为什么 VLA 会增加堆栈溢出风险的原因,在某些情况下会显着增加(警告:甚至不要考虑将 VLA 与递归函数调用结合使用!)。
这也是越界访问很可能导致问题的原因:一旦块结束,任何指向什么VLA 内存的东西都指向无效内存。
但从好的方面来说:这也是为什么这些数组是线程安全的(因为线程有自己的堆栈),以及为什么它们比堆内存更快。

VLA 的大小不能是:

  • 一个extern
  • 零或负

extern 限制是不言而喻的,非零、非负限制也是如此……但是:例如,如果指定 VLA 大小的变量是有符号整数,则编译器不会产生错误:VLA 的评估和分配是在运行时完成的,而不是在编译时完成的。因此VLA 的大小不能,也不必在 compile-time 期间给出
正如 MichaelBurr 正确指出的那样,VLA 与alloca内存非常相似,恕我直言,关键区别之一是:分配的内存alloca从分配点开始有效,并贯穿函数的其余部分。VLA 是块作用域的,因此一旦退出使用 VLA 的块,就会释放内存:

void alloca_diff( void )
{
    char *alloca_c, *vla_c;
    for (int i=1;i<10;++i)
    {
        char *alloca_mem = alloca(i*sizeof(*alloca_mem));
        alloca_c = alloca_mem;//valid
        char vla_arr[i];
        vla_c = vla_arr;//invalid
    }//end of scope, VLA memory is freed
    printf("alloca: %c\n", *alloca_c);//fine
    printf("vla: %c\n\", *vla_c);//undefined behaviour... avoid!
}//end of function alloca memory is freed, irrespective of block scope
于 2014-01-17T09:53:04.437 回答