【问题标题】:swapping 2 registers in 8086 assembly language(16 bits)用 8086 汇编语言交换 2 个寄存器(16 位)
【发布时间】:2026-01-19 08:25:01
【问题描述】:

有人知道如何在不使用其他变量、寄存器、堆栈或任何其他存储位置的情况下交换 2 个寄存器的值吗?谢谢!

就像交换 AX、BX。

【问题讨论】:

标签: assembly cpu-registers x86-16 16-bit


【解决方案1】:

您可以使用一些数学运算来做到这一点。我可以给你一个想法。希望对您有所帮助!

我遵循了这个 C 代码:

int i=10; j=20
i=i+j;
j=i-j;
i=i-j;

mov ax,10
mov bx,20
add ax,bx  
//mov command to copy data from accumulator to ax, I forgot the statement, now ax=30
sub bx,ax //accumulator vil b 10
//mov command to copy data from accumulator to bx, I forgot the statement now 
sub ax,bx //accumulator vil b 20
//mov command to copy data from accumulator to ax, I forgot the statement now 

【讨论】:

  • 到目前为止的汇编代码并不代表 C 代码!此外,当 AX 是累加器时,为什么要将数据从累加器复制到 ax??
  • 既然可以只使用 xchg,为什么还要推荐如此复杂的东西?
  • 尽管下面有*.com/a/47021804/552683,但将其作为公认的答案是相当误导!
  • @DavorCubranic:公平地说,在我写下下面的答案之前,这个低效的答案已经被接受了 3 年。但是 OP 仍然活跃在 SO 上,并且可以随时更改他们的接受投票。
【解决方案2】:

8086 对此有说明:

xchg   ax, bx

如果您确实需要交换两个 reg,xchg ax, bx 在大多数情况下是所有 x86 CPU 上最有效的方式,现代和古代包括 8086。(您可以构建一个案例,其中多个单-uop 指令可能更有效,因为由于周围代码导致的一些其他奇怪的前端效应。或者对于 32 位操作数大小,其中零延迟 mov 在 Intel CPU 上使用临时寄存器制作了一个 3-mov 序列更好)。

对于代码大小; xchg-with-ax 只占用一个字节。这就是 0x90 NOP 编码的来源:它是 xchg ax, ax,或 32 位模式下的 xchg eax, eax1。交换任何其他寄存器对需要 2 个字节用于 xchg r, r/m 编码。 (+ REX 前缀,如果在 64 位模式下需要。)

在实际的 8086 上,代码获取通常是性能瓶颈,因此 xchgfar 最好的方法,尤其是使用单字节 xchg-with-ax 缩写形式。

脚注 1:(在 64 位模式下,xchg eax, eax 会将 RAX 截断为 32 位,因此 0x90 明确是 nop 指令,不是也是 xchg)。


对于 32 位/64 位寄存器,3 条带有临时指令的 mov 指令可以从 mov-elimination 中受益,而 xchg 不能在当前的 Intel CPU 上使用。 xchg 在 Intel 上是 3 uop,它们都有 1c 延迟并且需要一个执行单元,所以一个方向有 2c 延迟,而另一个方向有 1c 延迟。有关当前 CPU 如何实现它的更多微架构细节,请参阅Why is XCHG reg, reg a 3 micro-op instruction on modern Intel architectures?

在 AMD Ryzen 上,32/64 位 regs 上的 xchg 是 2 微指令,并在重命名阶段处理,因此它就像两个并行运行的 mov 指令。在早期的 AMD CPU 上,它仍然是 2 uop 指令,但单程延迟为 1c。


xor-swapsadd/sub swapsmov 以外的任何其他多指令序列与 xchg 相比毫无意义 用于寄存器。它们都有 2 和 3 个周期的延迟,以及更大的代码大小。唯一值得考虑的是mov 说明。

或者更好的是,展开循环或重新排列代码以不需要交换,或者只需要 mov


用内存交换寄存器

请注意,xchg with memory 有一个隐含的 lock 前缀。不要不要xchg 与内存一起使用,除非性能根本不重要,但是代码大小可以。 (例如在引导加载程序中)。或者,如果您需要它是原子的和/或完整的内存屏障,因为它两者兼而有之。

(有趣的事实:隐式 lock 行为是 386 中的新行为。在 8086 到 286 中,xchg 与 mem 并不特殊,除非您使用 lock xchg,因此您可以有效地使用它。 但现代 CPU 即使在 16 位模式下也会将 xchg mem, reg 视为与 lock xchg 相同)

所以通常最有效的做法是使用另一个寄存器:

     ; emulate  xchg [mem], cx  efficiently for modern x86
   movzx  eax, word [mem]
   mov    [mem], cx
   mov    cx, ax

如果您需要用内存交换寄存器并且没有空闲的暂存寄存器,在某些情况下,xor-swap 可能是最佳选择。使用临时内存需要复制内存值(例如,使用push [mem] 复制到堆栈,或者在加载+存储内存操作数之前先将寄存器溢出到第二个临时内存位置。)

到目前为止,延迟最低的方式仍然是使用暂存寄存器;通常你可以选择一个不在关键路径上的,或者只需要重新加载(不是一开始就保存,因为值已经在内存中或者可以使用 ALU 指令从其他寄存器重新计算)。

; spill/reload another register
push  edx            ; save/restore on the stack or anywhere else

movzx edx, word [mem]    ; or just mov dx, [mem]
mov   [mem], ax
mov   eax, edx

pop   edx            ; or better, just clobber a scratch reg

用寄存器交换内存的另外两个合理(但更糟糕)的选择是:

  • 不接触任何其他寄存器(SP 除外):

      ; using scratch space on the stack
      push [mem]           ; [mem] can be any addressing mode, e.g. [bx]
      mov  [mem], ax
      pop  ax              ; dep chain = load, store, reload.
    
  • 或不碰其他任何东西:

      ; using no extra space anywhere
      xor  ax, [mem]
      xor  [mem], ax        ; read-modify-write has store-forwarding + ALU latency
      xor  ax, [mem]        ; dep chain = load+xor, (parallel load)+xor+store, reload+xor
    

使用两个内存目标xor 和一个内存源会降低吞吐量(更多存储和更长的依赖链)。

push/pop 版本仅适用于可以推送/弹出的操作数大小,但 xor-swap 适用于任何操作数大小。如果您可以在堆栈上使用临时文件,则保存/恢复版本可能更可取,除非您需要在代码大小和速度之间取得平衡。

【讨论】: