【问题标题】:Why does issuing empty asm commands swap variables?为什么发出空 asm 命令会交换变量?
【发布时间】:2020-02-28 22:29:00
【问题描述】:

所以我在搞乱内联汇编并使用 GCC 9 编译它。 结果是两个变量 a 和 b 被交换而没有实际发出任何直接命令。

#include<cstdio>
int main(){
    int a(1),b(2),c(3);
    asm ("": "=r"(c):"r"(a));
    asm ("": "=r"(a):"r"(b));
    asm ("": "=r"(b):"r"(c));
    printf("%d %d %d", a,b,c);
}

有人能解释一下这里发生了什么吗?

【问题讨论】:

  • 反汇编你的代码,你会发现。
  • @goodvibration:在这种情况下,这不是一个有用的答案;它没有告诉你为什么 gcc 做出了这些选择。您需要查看编译器的 asm 输出以找出哪些寄存器选择了 asm 语句,而不是反汇编。

标签: c++ gcc assembly inline-assembly


【解决方案1】:

变量分配的基本随机机会。 (或者实际上是 GCC 的内部机制的首选)。

您使用了仅输出 "=r" asm 操作数,但您的 asm 模板实际上并未写入该寄存器,因此您可以得到 GCC 选择的寄存器中的任何值。

这很像 C 中使用未初始化变量的未定义行为。

要查看发生了什么,将 asm cmets 放入 asm 模板中,在 asm 注释内扩展 %0%1。这对 GCC 如何进行寄存器分配没有影响:它不关心模板是否使用它隐式或显式选择的寄存器;您可以编写一个有用的模板并将其与操作数约束匹配。

将您的代码放在Godbolt compiler explorer, with gcc9.2 -O3 -fverbose-asm

.intel_syntax noprefix
.LC0:
        .string "%d %d %d"
main:
        sub     rsp, 8    #,
        mov     edi, OFFSET FLAT:.LC0     #,
        xor     eax, eax  #
        mov     ecx, 1    # tmp87,
        mov     esi, 2    # tmp89,
        nop  #ecx ecx   # c, tmp87
        nop  #esi esi   # a, tmp89
        nop  #edx ecx   # b, c
        call    printf  #
        xor     eax, eax  #
        add     rsp, 8    #,
        ret     

(而不是简单的 asm 注释,我将 cmets 放在 NOP 指令上,例如
asm ("nop #%0 %1": "=r"(c):"r"(a));,因此编译器-资源管理器过滤不会删除它们。asm 模板是纯文本替换,然后再将其提供给汇编程序这仍然完全等同于 GCC 使用相同选项编译原始源代码的方式。)

在前 2 种情况下,gcc 决定为输出和输入选择相同的寄存器,因此它们恰好像分配一样工作。

在第 3 种情况下,c 已经在寄存器中,将 3 放在任何地方都已被优化,因为 "=r"(c) 在读取之前覆盖了该值。

也许您在禁用优化的情况下进行编译?你也可以这样做,并关注发生的事情。 (可能 GCC 每次都会为输入和输出选择 eax)。我通常不会费心查看反优化的-O0 asm,因为它充满了存储/重新加载噪音。

【讨论】:

  • @A.Hristov:感谢您提出了一个格式清晰的问题,并提供了一个有趣的答案,这也是说明理解/调试 GNU C 内联 asm 语句的有用技术的好方法。 :)
猜你喜欢
  • 1970-01-01
  • 2015-06-13
  • 2019-08-06
  • 1970-01-01
  • 2022-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-08
相关资源
最近更新 更多