如何在没有MOV指令的情况下在寄存器之间移动数据?

Yur*_*Aps 0 x86 assembly

我怎样才能像这样移动:MOV EAX, EBX不使用MOV指令?

Pet*_*des 6

您可以使用lea简单的寄存器寻址模式作为较慢的模式mov(没有移动消除,并且在 Ice Lake 之前在 Intel 上运行较少的端口),尽管它仍然是一条指令。

# nasm -f elf64 foo.asm && objdump -drwC -Mintel foo.o
0000000000000000 <.text>:
   0:   89 c8                   mov    eax,ecx
   2:   8d 01                   lea    eax,[rcx]  # in 64-bit code,
   4:   67 8d 01                lea    eax,[ecx]  # don't use 32-bit address size
Run Code Online (Sandbox Code Playgroud)

除了速度较慢之外,某些寄存器(RSP/R12 和 RBP/R13)还需要额外的代码大小。(我使用了 64 位操作数大小,因此它们都需要 REX 前缀,例如 RSP 和 R12 机器代码排列起来,因为它们都需要使用 SIB 字节。

  10:   48 89 fe                mov    rsi,rdi
  13:   48 8d 37                lea    rsi,[rdi]
  16:   48 8d 34 24             lea    rsi,[rsp]
  1a:   49 8d 34 24             lea    rsi,[r12]
  1e:   48 8d 75 00             lea    rsi,[rbp+0x0]  # source had just [rbp]
  22:   49 8d 75 00             lea    rsi,[r13+0x0]
Run Code Online (Sandbox Code Playgroud)

当然,同样的事情也可以在其他模式下使用,用作lea eax, [ecx]2 字节指令。(或者 32 位寄存器在 16 位模式下为 4 个字节。即使您只需要 16 位寄存器,16 位模式也需要除 [bx|bp] + [si|di 之外的源寄存器的 32 位地址大小] 因为 16 位寻址模式编码限制。)


push/pop也是一个选项,使得仅用 2 个字节的机器代码(而不是通常的 3 个字节)复制 64 位寄存器成为可能。

或者,如果您不关心源寄存器(实际上是移动而不是复制),则可以xchg,当 EAX 是两个寄存器之一时,它的编码更短

  30:   52                      push   rdx
  31:   58                      pop    rax
  32:   48 89 d0                mov    rax,rdx
  35:   48 8d 02                lea    rax,[rdx]
  38:   87 f1                   xchg   ecx,esi    # opcode + ModRM form
  3a:   91                      xchg   ecx,eax    # EAX special case
Run Code Online (Sandbox Code Playgroud)

对于代码高尔夫很有用


其他愚蠢的计算机技巧包括imul评论中的双班建议

  • 立即imul为 1。

  • 对于 16 位寄存器,可以使用计数 = 16 的SHLD或 SHRD。(x86 标量整数移位会屏蔽 64 位以外的任何操作数大小的计数& 31,因此这无法移位 32 或 64 位完整寄存器的所有位,只能移位 16 位部分寄存器。并且 shld /shrd 是 386 中的新增内容,因此 16 位寄存器始终是“部分”的。)

  40:   6b f1 01                imul   esi,ecx,0x1
  43:   66 0f a4 ce 10          shld   si,cx,0x10    # SI = CX.  CX unchanged.
  48:   66 0f ac ce 10          shrd   si,cx,0x10
  4d:   0f a4 ce 20             shld   esi,ecx,0x20  # nope, equivalent to a shift by 0
Run Code Online (Sandbox Code Playgroud)

或者,如果您想考虑多个指令,您可以像 Yuri 建议的那样将目的地和addor或 异或xor归零。+零是、|和的单位元^

效率更低的是将 AND 转换为全 1,即 的单位元素&。(错误地依赖于 EAX 的旧值,并且它不是异或归零,因此无法像 Sandybridge 系列 CPU 那样消除(无执行单元)。代码大小也更大)

  or   eax, -1
  and  eax, ecx
Run Code Online (Sandbox Code Playgroud)

Godbolt编译器资源管理器 NASM源代码和反汇编,用于本答案中的反汇编)

  • 这是家庭作业的一个挑战,我真的从你的帖子中学到了很多东西 (2认同)