GCC 如何实现变长数组?
Posted
技术标签:
【中文标题】GCC 如何实现变长数组?【英文标题】:How does GCC implement variable-length arrays? 【发布时间】:2014-02-06 13:42:54 【问题描述】:GCC 如何实现可变长度数组 (VLA)?这样的数组本质上是指向alloca返回的动态分配存储的指针吗?
我能想到的另一种选择是,将这样的数组分配为函数中的最后一个变量,以便在编译时知道变量的偏移量。但是,第二个 VLA 的偏移量将在编译时再次未知。
【问题讨论】:
VLA works by placing the array in the stack
- ***.com/questions/2034712/variable-length-arrays。这也是我在使用 VLA 时检查 gcc 生成的程序集输出时看到的,没有调用 malloc
。但这可能取决于实际的实现。
这是一个开源项目。你可以阅读代码。或者,您可以简单地通过检查它省略的代码来解决它。另请注意,完全有可能在不同的平台上有不同的实现。
实现使用malloc
来实现VLA 没有任何意义,因为malloc 可能会失败。如果有足够的可用堆栈空间,则可以保证分配 VLA 成功。 malloc 永远不能保证成功。
@Brandin:分配可变长度数组或通过malloc
分配都不能保证无限期地工作。在最常见的 C 实现中,使用malloc
处理可变长度数组将支持比使用堆栈更大的可变长度数组,因为可用于动态分配的空间远大于默认堆栈大小。
@Brandin:除此之外,大多数 C 实现不提供任何关于堆栈空间例程将使用多少的保证,不提供任何帮助来检查编译结果以查看它们有多少使用,并且不支持运行时检查已使用了多少堆栈空间(尽管显然可以将堆栈指针的值与堆栈限制进行比较,前提是已经研究了实现并使用了非标准代码)。因此,没有支持的方法来防止创建可变长度数组的灾难性失败。程序简单地中止。
【参考方案1】:
好吧,根据 VLA 的限制,这些只是黑暗中的一些狂野刺,但无论如何:
VLA 不能是:
外部 结构成员 静态 未指定边界声明(保存为函数原型)所有这些都表明 VLA 被分配在 堆栈 上,而不是堆上。所以是的,VLA 可能是分配新块时分配的最后一块堆栈内存(块在块范围中,这些是循环、函数、分支或其他)。 这也是 VLA 会增加堆栈溢出风险的原因,在某些情况下会显着增加(警告:甚至不要考虑将 VLA 与递归函数调用结合使用!)。 这也是越界访问很可能导致问题的原因:一旦块结束,任何指向 Was VLA 内存的东西都指向无效内存。 但是有利的一面:这也是为什么这些数组是线程安全的(因为线程有自己的堆栈),以及为什么它们比堆内存更快。
VLA 的大小不能是:
extern
值
零或负数
extern 限制是不言而喻的,非零非负限制也是如此……但是:如果指定 VLA 大小的变量是有符号整数,例如,编译器不会产生错误:VLA 的评估和分配是在运行时完成的,而不是在编译时完成的。因此,VLA 的大小不能,也不必在编译时给定。
正如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
【讨论】:
【参考方案2】:这是取自some GCC docs for VLA support的以下示例行的分配代码(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()
。
【讨论】:
我认为alloca
是模拟 VLA 支持的 "fix",但有一些细微的差别,比如 VLA 是按块进行 GC,alloca
内存按功能
@EliasVanOotegem:我认为 alloca()
早于 VLA 支持。此外,alloca()
在函数返回时被释放,例如,循环中的alloca()
调用将增大堆栈。循环中的 VLA 将在每次迭代中被释放和重新分配。此外,正如 VLA 的 GCC 文档页面中所述,alloca()
和 VLA 不一定能很好地配合使用(释放 VLA 可以在函数返回之前“释放”alloca()
分配)。
我想知道当 -fomit-frame-pointer
被传递时这是如何工作的
@HansPassant:我猜 MinGW 的人在他们的库中添加了一个 __chkskk_ms
函数来支持相同的功能(并允许将 MSVC 生成的目标文件链接到 MinGW 运行时)。
这是未优化的代码。没有理智的编译器会使用 div
或 imul
来获得 2 的编译时间常数幂,除非有时禁用优化。这只是因为 GCC 的 VLA 的固定操作序列在 处理编译时常量的通常转换传递之后被替换。显然它是用x / 16 * 16
而不是x &= -16
写成的,以向下舍入到下一个16 的倍数。以上是关于GCC 如何实现变长数组?的主要内容,如果未能解决你的问题,请参考以下文章