gla*_*des 45 c alloca stack-frame variable-length-array
我试图弄清楚alloca()
在记忆层面上实际上是如何工作的。来自Linux 手册页:
alloca() 函数在调用者的堆栈帧中分配 size 字节的空间。当调用 alloca() 的函数返回到其调用者时,该临时空间会自动释放。
这是否意味着将按字节alloca()
转发堆栈指针n
?或者说新创建的内存到底分配在哪里?
这不是与可变长度数组完全相同吗?
我知道实现细节可能留给操作系统之类的东西。但我想知道一般来说这是如何实现的。
dbu*_*ush 30
是的,alloca
在功能上等同于局部可变长度数组,即:
int arr[n];
Run Code Online (Sandbox Code Playgroud)
和这个:
int *arr = alloca(n * sizeof(int));
Run Code Online (Sandbox Code Playgroud)
两者都为堆栈上的n
类型元素分配空间。int
每种情况之间的唯一区别arr
是 1) 一个是实际数组,另一个是指向数组第一个元素的指针,2) 数组的生命周期以其封闭范围结束,而内存alloca
的生命周期在函数执行时结束返回。在这两种情况下,数组都驻留在堆栈上。
例如,给出以下代码:
#include <stdio.h>
#include <alloca.h>
void foo(int n)
{
int a[n];
int *b=alloca(n*sizeof(int));
int c[n];
printf("&a=%p, b=%p, &c=%p\n", (void *)a, (void *)b, (void *)c);
}
int main()
{
foo(5);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
当我运行这个时,我得到:
int arr[n];
Run Code Online (Sandbox Code Playgroud)
这表明返回的内存alloca
位于两个 VLA 的内存之间。
VLA 首次出现在 C99 的 C 标准中,但alloca
早在这之前就已经存在了。Linux 手册页指出:
符合
该函数不在 POSIX.1-2001 中。
有证据表明alloca()函数出现在32V、PWB、PWB.2、3BSD和4BSD中。4.3BSD 中有一个它的手册页。Linux使用GNU版本。
BSD 3 的历史可以追溯到 70 年代末,因此alloca
在将 VLA 添加到标准之前,它也是对 VLA 的早期非标准化尝试。
如今,除非您使用的编译器不支持 VLA(例如 MSVC),否则实际上没有理由使用此函数,因为 VLA 现在是获得相同功能的标准化方法。
tst*_*isl 17
另一个答案精确地描述了 VLA 和 的机制alloca()
。
alloca()
然而,VLA和自动VLA之间存在显着的功能差异。对象的生命周期。
如果alloca()
函数返回时生命周期结束。对于 VLA,对象在包含块结束时被释放。
char *a;
int n = 10;
{
char A[n];
a = A;
}
// a is no longer valid
{
a = alloca(n);
}
// is still valid
Run Code Online (Sandbox Code Playgroud)
因此,可以轻松地耗尽循环中的堆栈,而 VLA 则无法做到这一点。
for (...) {
char *x = alloca(1000);
// x is leaking with each iteration consuming stack
}
Run Code Online (Sandbox Code Playgroud)
与
for (...) {
int n = 1000;
char x[n];
// x is released
}
Run Code Online (Sandbox Code Playgroud)
虽然alloca从语法的角度看起来像一个函数,但它不能在现代编程环境中作为普通函数来实现*。它必须被视为具有类似函数接口的编译器功能。
传统上,C 编译器维护两个指针寄存器,一个“堆栈指针”和一个“帧指针”(或基指针)。堆栈指针界定堆栈的当前范围。帧指针在函数入口处保存堆栈指针的值,用于访问局部变量并在函数退出时恢复堆栈指针。
如今,大多数编译器在普通函数中默认不使用帧指针。现代调试/异常信息格式使其变得不必要,但他们仍然了解它是什么并且可以在需要时使用它。
特别是对于具有分配或可变长度数组的函数,使用帧指针允许函数跟踪其堆栈帧的位置,同时动态修改堆栈指针以适应可变长度数组。
例如我在O1为arm构建了以下代码
#include <alloca.h>
int bar(void * baz);
void foo(int a) {
bar(alloca(a));
}
Run Code Online (Sandbox Code Playgroud)
并得到了(我的评论)
foo(int):
push {fp, lr} @ save existing link register and frame pointer
add fp, sp, #4 @ establish frame pointer for this function
add r0, r0, #7 @ add 7 to a ...
bic r0, r0, #7 @ ... and clear the bottom 3 bits, thus rounding a up to the next multiple of 8 for stack alignment
sub sp, sp, r0 @ allocate the space on the stack
mov r0, sp @ make r0 point to the newly allocated space
bl bar @ call bar with the allocated space
sub sp, fp, #4 @ restore stack pointer from frame pointer
pop {fp, pc} @ restore frame pointer to value at function entry and return.
Run Code Online (Sandbox Code Playgroud)
是的,分配和可变长度数组非常相似(尽管另一个答案指出不完全相同)。alloca 似乎是这两个构造中较旧的一个。
* 使用足够愚蠢/可预测的编译器,可以将 alloca 实现为汇编程序中的函数。具体来说,编译器需要。
这显然是它最初的实现方式(https://www.tuhs.org/cgi-bin/utree.pl?file=32V/usr/src/libc/sys/alloca.s)。
我想也可能将实际实现作为汇编器函数,但编译器中有一种特殊情况,当它看到 alloca 时,它会进入愚蠢/可预测模式,我不知道是否有任何编译器供应商这样做。