我不相信有一种可靠的方法只使用编译选项来解决这个问题。更可取的机制是无论用于编译的选项如何,都可以在编译器的未来版本上完成这项工作和工作。
关于已接受答案的评论
在接受的答案中,对原文进行了编辑,建议使用此解决方案:
int main(void) {
__asm__ ("jmp exit");
handler:
__asm__ __volatile__("jmp $0x0");
exit:
return 0;
}
首先jmp $0x0
应该是jmp 0x0
。其次, C标签通常被翻译成本地标签。jmp exit
实际上并没有跳转到Cexit
函数中的标签,而是跳转到C库中的函数,有效地绕过了. 将Godbolt 与 GCC 4.6.4 一起使用,我们得到了这个未优化的输出(我已经修剪了我们不关心的标签):exit
return 0
main
main:
pushl %ebp
movl %esp, %ebp
jmp exit
jmp 0x0
.L3:
movl $0, %eax
popl %ebp
ret
.L3
实际上是 的本地标签exit
。您不会exit
在生成的程序集中找到标签。如果存在C库,它可以编译和链接。不要像这样在内联汇编中使用C本地 goto 标签。
使用 asm goto 作为解决方案
从 GCC 4.5(OP 使用 4.6.x)开始,支持asm goto
扩展程序集模板。asm goto
允许您指定内联程序集可能使用的跳转目标:
6.45.2.7 转到标签
asm goto允许汇编代码跳转到一个或多个 C 标签。asm goto 语句中的 GotoLabels 部分包含一个逗号分隔的所有 C 标签列表,汇编代码可以跳转到这些标签。GCC 假定 asm 执行落入下一条语句(如果不是这种情况,请考虑在 asm 语句之后使用 __builtin_unreachable 内在函数)。可以通过使用热标签属性和冷标签属性来改进 asm goto 的优化(请参阅标签属性)。
asm goto 语句不能有输出。这是由于编译器的内部限制:控制转移指令不能有输出。如果汇编程序代码确实修改了任何内容,请使用“内存”破坏程序强制优化器将所有寄存器值刷新到内存,并在 asm 语句之后重新加载它们(如有必要)。
另请注意,asm goto 语句始终被隐式视为易失性。
要引用汇编器模板中的标签,请在其前面加上“%l”(小写“L”),后跟其在 GotoLabels 中的(从零开始的)位置加上输入操作数的数量。例如,如果 asm 有三个输入并引用两个标签,则将第一个标签称为“%l3”,第二个称为“%l4”)。
或者,您可以使用括号中的实际 C 标签名称来引用标签。例如,要引用名为 carry 的标签,您可以使用 '%l[carry]'。使用此方法时,标签仍必须列在 GotoLabels 部分中。
代码可以这样写:
int main(void) {
__asm__ goto ("jmp %l[exit]" :::: exit);
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
我们可以使用asm goto
. 我更喜欢__asm__
,因为如果使用or选项asm
编译它不会抛出警告。在clobbers 之后,您可以列出内联程序集可能使用的跳转目标。C实际上并不知道我们是否跳转,因为 GCC 不会分析内联汇编模板中的实际代码。它不能删除这个跳转,也不能假设后面是死代码。将Godbolt 与 GCC 4.6.4 一起使用,未优化的代码(修剪)如下所示:-ansi
-std=?
main:
pushl %ebp
movl %esp, %ebp
jmp .L2 # <------ this is the goto exit
jmp 0x0
.L2: # <------ exit label
movl $0, %eax
popl %ebp
ret
带有 GCC 4.6.4 输出的Godbolt看起来仍然正确,并显示为:
main:
jmp .L2 # <------ this is the goto exit
jmp 0x0
.L2: # <------ exit label
xorl %eax, %eax
ret
无论您是否启用了优化,该机制也应该可以工作,并且无论您是为 64 位还是 32 位 x86 目标编译都无关紧要。
其他观察
当扩展内联汇编模板中没有输出约束时,该asm
语句是隐式可变的。线
__asm__ __volatile__("jmp 0x0");
可以写成:
__asm__ ("jmp 0x0");
asm goto
语句被认为是隐含的易失性。它们也不需要volatile
修饰符。