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

How to move data between registers without MOV instruction?

如何在不使用 MOV 指令的情况下像 MOV EAX, EBX 那样移动?

首先清理一个寄存器XOR EAX, EAX上的数据,然后执行OR EAX, EBX

您可以将 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

除了速度较慢之外,某些寄存器 () 还需要额外的代码大小。 (我使用了 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]

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


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

或者如果你不关心源寄存器(实际上是移动而不是复制),你可以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

这是useful for code-golf


其他愚蠢的计算机技巧包括 imul 和来自评论的双班制建议

  • 立即 imul 1.

  • 对于 16 位寄存器,SHLD 或计数 = 16 的 SHRD 是可能的。 (对于 64 位以外的任何操作数大小,x86 标量整数移位用 & 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

或者如果您想考虑多条指令,您可以像 Yuri 建议的那样将目标和 addorxor 异或归零。零是 +|^.

的标识元素

将 AND 转换为全一(& 的标识元素)效率更低。 (对 EAX 的旧值的错误依赖,并且它不是异或归零,所以它不能像 Sandybridge-family CPU 那样被消除(没有执行单元)。代码大小也更大)

  or   eax, -1
  and  eax, ecx

(Godbolt compiler explorer NASM 来源和反汇编,用于本答案中的反汇编)