我知道那种感觉,然后每个人都说不应该这样做;它只是必须完成。在 GNU C 中用于&&the_label;
获取标签的地址。( https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.htmlgoto *ptr
) 你猜到的语法void*
实际上是 GNU C 使用的。
或者如果你出于某种原因想使用内联汇编,这里是如何使用GNU Casm goto
// unsafe: this needs to use asm goto so the compiler knows
// execution might not come out the other side
#define unsafe_jumpto(a) asm("jmp *%0"::"r"(a):)
// target pointer, possible targets
#define jumpto(a, ...) asm goto("jmp *%0" : : "r"(a) : : __VA_ARGS__)
int main (void)
{
int i=1;
void* the_label_pointer;
the_label:
the_label_pointer = &&the_label;
label2:
if( i-- )
jumpto(the_label_pointer, the_label, label2, label3);
label3:
return 0;
}
标签列表必须包含 的所有可能值the_label_pointer
。
宏扩展将类似于
asm goto("jmp *%0" : : "ri"(the_label_pointer) : : the_label, label2, label3);
这与 gcc 4.5 及更高版本以及最新的 clang 一起编译,该 clangasm goto
在 clang 8.0 之后的一段时间内得到了支持。 https://godbolt.org/z/BzhckE。对于 GCC9.1,生成的 asm 看起来像这样,它优化了i=i
/的“循环” ,i--
只是把. 所以它仍然只运行一次,就像在 C 源代码中一样。the_label
jumpto
# gcc9.1 -O3 -fpie
main:
leaq .L2(%rip), %rax # ptr = &&label
jmp *%rax # from inline asm
.L2:
xorl %eax, %eax # return 0
ret
但是 clang 没有做那个优化并且仍然有循环:
# clang -O3 -fpie
main:
movl $1, %eax
leaq .Ltmp1(%rip), %rcx
.Ltmp1: # Block address taken
subl $1, %eax
jb .LBB0_4 # jump over the JMP if i was < 1 (unsigned) before SUB. i.e. skip the backwards jump if i wrapped
jmpq *%rcx # from inline asm
.LBB0_4:
xorl %eax, %eax # return 0
retq
标签地址运算符 && 仅适用于 gcc。显然,需要为每个处理器专门实现 jumpto 汇编宏(这个宏适用于 32 位和 64 位 x86)。
还要记住,(没有asm goto
)不能保证堆栈的状态在同一个函数的两个不同点是相同的。并且至少在打开一些优化的情况下,编译器可能会假设一些寄存器在标签之后的点包含一些值。这类事情很容易搞砸,然后做编译器没想到的疯狂事情。请务必校对已编译的代码。
这就是为什么asm goto
有必要通过让编译器知道你将/可能跳转到哪里来确保它的安全,为跳转和目的地获得一致的代码生成。