GCC 如何实现变长数组(VLA)?这样的数组是否本质上是指向动态分配存储的指针,例如由 alloca 返回的?
我能想到的另一种选择是,将这样的数组分配为函数中的最后一个变量,以便在编译时知道变量的偏移量。但是,第二个 VLA 的偏移量将在编译时再次未知。
GCC 如何实现变长数组(VLA)?这样的数组是否本质上是指向动态分配存储的指针,例如由 alloca 返回的?
我能想到的另一种选择是,将这样的数组分配为函数中的最后一个变量,以便在编译时知道变量的偏移量。但是,第二个 VLA 的偏移量将在编译时再次未知。
以下是从一些 GCC 文档中获取的 VLA 支持示例行的分配代码(x86 - x64 代码类似) :
char str[strlen (s1) + strlen (s2) + 1];
计算在哪里strlen (s1) + strlen (s2) + 1
(eax
GCC 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()
.
好吧,根据 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