【问题标题】:"shld" instruction produces weird value“shld”指令产生奇怪的值
【发布时间】:2026-01-08 12:10:01
【问题描述】:

我在内联汇编中使用“shld”指令,由 g++ (7.3.0) 编译。 它会产生一些奇怪的结果。

在 Ubuntu 和 WSL 上试过。

   unsigned long long hi, lo;
   //some code goes here
   //...
   asm volatile (
    "shld $0x3, %1, %0;\n"
    : "=r"(hi)
    : "r"(lo)
    :
    );
   //I expect the asm produces this:
   //hi = (hi << 3) | (lo >> 61);
   //but the actual result is:
   //hi = (lo << 3) | (lo >> 61);
   //you can see the real assembly produced by gcc below.

我希望“hi”中的结果值是

(hi << 3) | (lo >> 61)

但实际结果是

(lo << 3) | (lo >> 61)

详情请见https://en.wikibooks.org/wiki/X86_Assembly/Shift_and_Rotate

原来g++把我的代码翻译成这样:

    6e6a:       48 8b 45 a0             mov    -0x60(%rbp),%rax
    6e6e:       48 0f a4 c0 03          shld   $0x3,%rax,%rax
    6e73:       48 89 45 98             mov    %rax,-0x68(%rbp)

其中 -0x60(%rbp) 是“lo”,-0x68(%rbp) 是“hi”。

【问题讨论】:

  • 因为我的代码会修改变量“hi”
  • 是的,它解决了问题,但看起来很奇怪。
  • 我可以在没有任何优化选项的情况下通过测试,但是当我打开优化器 ("-O3") 时,程序会产生不同的结果并且测试失败。
  • @rcgldr, "=r" 表示操作数只是一个输出。 “+r”用于表示既是输入又是输出的操作数。 “r”表示输入。
  • @PeterCordes - 已删除,稍后将删除。

标签: c++ c gcc assembly inline-assembly


【解决方案1】:

您需要指定hi 既是输入又是输出。像这样的:

asm volatile (
"shld $0x3, %1, %0;\n"
: "=r"(hi)
: "r"(lo)
, "0"(hi)
:
);

生成以下汇编代码:

mov    -0x10(%rbp),%rdx
mov    -0x8(%rbp),%rax
shld   $0x3,%rdx,%rax
mov    %rax,-0x8(%rbp)

“0”表示这个操作数(2号)必须和0号操作数相同,这看起来没什么用,只是它让这个寄存器既是输入又是输出。

【讨论】:

  • 更好的是"+r"(hi) 表示输入和输出的值相同。
  • 谢谢。我还通过将更多 c++ 代码转换为汇编代码来解决我的问题,使“hi”只出现在输入中。
  • 只有当输出应该进入一个 不同的 变量时使用"0"(var)匹配约束才有意义,即使您修改了寄存器,原始输入 C 变量也不会被修改它到达了。正如 prl 所说,"+r" 是您在这种实际 RMW 操作数的情况下想要的,更易于阅读。
  • @PeterCordes 请提交您自己的答案。
  • 我可以,但我很好奇你为什么不同意它是更好的风格并且会改进你的风格。无论哪种方式,我认为 PRL 的评论足以显示最简单的最终结果,并且您的答案的英文文本已经 100% 清楚地回答了这个问题。