The problems you'd see from failing to tell the compiler about registers you modify would be exactly the same as if you wrote a function in asm that modified some call-preserved registers1. See more explanation and a partial example in Why should certain registers be saved? What could go wrong if not?
在 GNU 内联汇编中,all假定保留寄存器,编译器选择的寄存器除外"=r"
/ "+r"
或其他输出操作数。编译器可能会在任何寄存器中保留一个循环计数器,或者稍后要读取的任何其他内容,并期望它仍然具有在来自 asm 模板的指令之前放置的值。 (禁用优化后,编译器不会跨语句将变量保留在寄存器中,但当您使用-O1
或更高。)
除了属于某个内存的一部分的位置之外,所有内存都相同"=m"
or "+m"
内存输出操作数。 (除非你使用"memory"
破坏。)参见如何指示可以使用内联 ASM 参数*指向*的内存?更多细节。
脚注1:
与函数不同,您应该not使用您自己的指令在 asm 模板中保存/恢复任何寄存器。只需告诉编译器它就可以保存/恢复在整个函数的开始/结束处内联后,并避免在其中包含任何需要的值。事实上,在具有红色区域的 ABI(如 x86-64 System V)中,使用push
/pop
在 asm 内部将具有破坏性:在 C++ 内联汇编中使用基址指针寄存器
GNU C inline asm 的设计理念是它使用与编译器内部机器描述文件相同的语法。标准用例是用于包装single指令,这就是为什么如果模板字符串中的 asm 代码在写入某些寄存器之前没有读取其所有输入,则需要早期破坏声明。
模板对于编译器来说是一个黑匣子;您需要向优化编译器准确地描述它。任何错误实际上都是未定义的行为,并且为编译器留下了混乱周围代码中其他变量的空间,甚至可能在调用此变量的函数中(如果您修改了编译器未使用的调用保留寄存器)。
这使得仅通过测试来验证正确性是不可能的。您无法区分“正确”和“恰好适用于周围的代码和编译器选项集”。这就是为什么您应该避免使用内联汇编的原因之一,除非好处大于缺点和错误风险。https://gcc.gnu.org/wiki/DontUseInlineAsm
GCC 只是对模板字符串进行字符串替换,非常类似于 printf,并将整个结果(包括编译器生成的纯 C 代码指令)作为单个文件发送到汇编器。看看https://godbolt.org/有时候;即使内联汇编中有无效指令,编译器本身也不会注意到。只有真正组装的时候才会出现问题。 (编译器浏览器站点上的“二进制”模式。)
也可以看看https://stackoverflow.com/tags/inline- assembly/info获取更多指南链接。