输出是不是总是由 C 中的内联汇编中的 %eax 寄存器确定?

Posted

技术标签:

【中文标题】输出是不是总是由 C 中的内联汇编中的 %eax 寄存器确定?【英文标题】:Is output always determined by the %eax register in inline assembly in C?输出是否总是由 C 中的内联汇编中的 %eax 寄存器确定? 【发布时间】:2020-02-17 05:32:25 【问题描述】:

我正在阅读有关 C 中的内联汇编的教程,他们尝试使用

进行简单的变量赋值
int a=10, b;
asm ("movl %1, %%eax; 
movl %%eax, %0;"
:"=r"(b)        /* output */
:"r"(a)         /* input */
:"%eax"         /* clobbered register */
);

这对我来说很有意义(将输入移至 eax,然后将 eax 移至输出)。但是当我删除 %movl %%eax, 0 行(应该将正确的值移动到输出)时,变量 b 仍然从内联程序集中分配了正确的值。

我的主要问题是输出如何“知道”从这个 %eax 寄存器中读取?

【问题讨论】:

顺便说一句,问题中的 asm 语句实际上是完全安全和正确的。至少我看不出有什么问题。这在有关内联汇编的问题中很少见,所以我认为值得一提! 【参考方案1】:

内联汇编语句不是函数调用

“在 EAX 中返回”是针对函数的;它是调用约定的一部分,它允许编译器生成可以与其他代码交互的代码,即使它们是单独编译的。调用约定被定义为 ABI 文档的一部分。

除了定义如何返回(例如,EAX 中的小型非 FP 对象、XMM0 或 ST0 中的浮点)之外,它们还定义了调用者将 args 放在哪里,以及您可以在不保存/恢复的情况下使用哪些寄存器(call-clobbered ) 并且您可以(保留通话)。有关 x86 调用约定的更多信息,请参阅https://en.wikipedia.org/wiki/Calling_convention 和https://www.agner.org/optimize/calling_conventions.pdf。

这套不灵活的严格规则不适用于内联汇编,因为它不必;编译器必须可以将 asm 语句视为周围 C 代码的一部分。这将破坏 inline 的全部意义。相反,在 GNU C 内联 asm 中,您编写向编译器描述 asm 的操作数/约束,有效地为每个 asm 语句创建自定义调用约定。 (该约定的一部分由编译器选择 "=r" 输出。如果您想强制它选择 AL/AX/EAX/RAX,请使用 "=a"。)

如果您想编写在 EAX 中返回的 asm,而不必告诉编译器,请编写一个独立函数。 (例如,在.s 文件中,或作为__attribute__((naked)) C 函数主体的asm("") 语句中。无论哪种方式,您都必须自己编写ret 并通过调用约定获取args。)

在运行 asm 语句并在 EAX 中留下一个值后,从非 void 函数的末尾脱落可能会在禁用优化的情况下工作,但它是完全不安全的,并且会在您启用优化和编译器内联它。


我的主要问题是输出如何“知道”从这个 %eax 寄存器中读取?

当您在禁用优化的情况下编译时,它可能只是碰巧为"=r" 输出选择了 EAX。 EAX 始终是 GCC 评估表达式的首选。 查看编译器生成的 asm 输出 (gcc -S -fverbose-asm) 以了解它在您的 asm 周围生成了哪些 asm,以及将其替换到您的 asm 模板中的哪些寄存器。你可能有 mov %eax, %eaxmov %eax, %eax.

使用mov 作为 asm 模板的第一条或最后一条指令几乎总是意味着您做错了,应该使用更好的约束来告诉编译器将数据放在哪里或在哪里找到您的数据。

例如asm("" : "=r"(b) : "0"(a)) 将使编译器将输入放入与期望输出操作数相同的寄存器中。这样就复制了一个值。 (并强制编译器在寄存器中实现它,并忘记它所知道的关于当前值的任何信息,从而破坏常量传播和值范围优化,以及阻止编译器完全优化该临时值。)

Why does issuing empty asm commands swap variables? 描述了通过更改发生的情况,与编译器为输入和输出 "r" 操作数选择相同的 reg 的情况相同。并说明在 asm 模板中使用 asm cmets * 打印出编译器为您未明确引用的任何 %0%1 操作数选择的内容**。

有关使用输入和输出约束的基础知识的更多信息,另请参阅 segmentation fault(core dumped) error while using inline assembly。

还相关:What happens to registers when you manipulate them using asm code in C++? 是另一个示例,并记录了编译器如何处理 GNU C 内联 asm 语句中的寄存器。

【讨论】:

这更有意义,也更清楚了,感谢您的深入回应! @Tom:如果这完全回答了您的问题,您可以使用向上/向下投票箭头下的复选标记将其标记为已接受。很高兴我能帮上忙;你的问题很清楚你的误解是什么,可以写一个答案:) @Tom:刚刚注意到我链接了错误的问答:本意是链接 segmentation fault(core dumped) error while using inline assembly 而不是 Inline assembly multiplication "undefined reference" on inputs。也许后一个问题中的错误代码导致您在下一个问题中使用$1 而不是%1

以上是关于输出是不是总是由 C 中的内联汇编中的 %eax 寄存器确定?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 多线程内联汇编

x86 - 使用内联汇编设置位

GCC 扩展内联汇编简介

QT如何内嵌汇编?

VC内联汇编,引用程序中的变量

内联汇编_把a值赋给b的汇编代码