在GCC中,您可以通过获取标签的地址(如void *addr = &&label
中)然后跳转到它(jump *addr
)来使用计算的转到。 GCC manual表示你可以从函数的任何地方跳转到这个地址,只是从另一个函数跳转到它是未定义的。
当你跳转到代码时,它不能假设寄存器的值,因此可能会从内存中重新加载它们。但是,堆栈指针的值也不一定是定义的,例如,您可能会从声明额外变量的嵌套范围跳转。
问题是GCC如何设法将堆栈指针的值设置为正确的值(可能太高或太低)?它是如何与-fomit-frame-pointer
(如果它)相互作用的?
最后,对于额外的积分,你可以从哪里跳到标签的真正限制是什么?例如,您可以从中断处理程序执行此操作。
答案 0 :(得分:12)
通常,当你有一个带有地址的标签的函数时,gcc需要确保你可以从函数中的任何间接goto跳转到那个标签 - 所以它需要布局堆栈以便精确的堆栈指针并不重要(所有内容都从帧指针索引),或者堆栈指针在所有指针上都是一致的。通常,这意味着它在函数启动时分配固定数量的堆栈空间,之后从不接触堆栈指针。因此,如果您有带变量的内部作用域,则空间将在函数start处分配,并在函数结束时释放,而不是在内部作用域中。只需要将构造函数和析构函数(如果有)绑定到内部作用域。
跳转到标签的唯一限制是你注意到的那个 - 你只能在包含标签的功能中进行。不是来自任何其他函数或中断处理程序的任何其他堆栈帧或任何东西。
修改强>
如果您希望能够从一个堆栈帧跳转到另一个堆栈帧,则需要使用setjmp / longjmp或类似的东西来展开堆栈。你可以将它与间接goto结合起来 - 比如:
if (target = (void *)setjmp(jmpbuf)) goto *target;
这样你可以从任何被调用的函数调用longjmp(jmpbuf, label_address);
来展开堆栈,然后跳转到标签。只要setjmp/longjmp
从中断处理程序工作,这也可以从中断处理程序工作。也取决于sizeof(int) == sizeof(void *)
,但并非总是如此。
答案 1 :(得分:2)
我不认为计算goto的事实会增加它对局部变量的影响。局部变量的生命周期从在声明处或声明之外输入声明开始,到无法以任何方式到达变量范围时结束。这包括所有不同类型的控制流,特别是goto
和longjmp
。所以所有这些变量总是安全的,直到从声明它们的函数返回。
C中的标签对于整个englobing函数是可见的,因此如果这是计算的goto
,则没有太大区别。您始终可以使用或多或少的goto
语句替换计算的switch
。
本规则对局部变量的一个值得注意的例外是可变长度数组VLA。由于做必然会更改堆栈指针,因此它们具有不同的规则。退出声明块后,生命周期结束,并且在声明可变修改类型后,goto
和longjmp
不允许进入作用域。
答案 2 :(得分:0)
在函数序言中,即使使用-fomit-frame-pointer,堆栈的当前位置也会保存在被调用者保存的寄存器中。
在下面的例子中,sp + 4存储在r7中,然后在结尾(LBB0_3)中恢复(r7 + 4 - > r4; r4 - > sp)。因此,您可以跳转到函数内的任何位置,在函数中的任何位置增加堆栈,而不是搞砸堆栈。如果你跳出这个功能(通过jump * addr),你将跳过这个结尾,并且皇家地搞砸了堆栈。
简短示例,它还使用alloca在堆栈上动态分配内存:
clang -arch armv7 -fomit-frame-pointer -c -S -O0 -o - stack.c
#include <alloca.h>
int foo(int sz, int jmp) {
char *buf = alloca(sz);
int rval = 0;
if( jmp ) {
rval = 1;
goto done;
}
volatile int s = 2;
rval = s * 5;
done:
return rval;
}
和反汇编:
_foo:
@ BB#0:
push {r4, r7, lr}
add r7, sp, #4
sub sp, #20
movs r2, #0
movt r2, #0
str r0, [r7, #-8]
str r1, [r7, #-12]
ldr r0, [r7, #-8]
adds r0, #3
bic r0, r0, #3
mov r1, sp
subs r0, r1, r0
mov sp, r0
str r0, [r7, #-16]
str r2, [r7, #-20]
ldr r0, [r7, #-12]
cmp r0, #0
beq LBB0_2
@ BB#1:
movs r0, #1
movt r0, #0
str r0, [r7, #-20]
b LBB0_3
LBB0_2:
movs r0, #2
movt r0, #0
str r0, [r7, #-24]
ldr r0, [r7, #-24]
movs r1, #5
movt r1, #0
muls r0, r1, r0
str r0, [r7, #-20]
LBB0_3:
ldr r0, [r7, #-20]
subs r4, r7, #4
mov sp, r4
pop {r4, r7, pc}