从内存复制到内存的正常/有效方法是加载到临时寄存器中。选一个;如果您在复制后仍然不需要寄存器中的加载地址,您甚至可以movl (%ecx), %ecx / movl %ecx, (%eax)。
还有其他方法,例如pushl (%ecx) / popl (%edx) 或为movsd 设置 RSI/ESI 和 RDS/EDI,但速度较慢;通常最好只释放一个临时寄存器,即使这意味着稍后重新加载某些内容,或者甚至存储/重新加载一些其他不常用的值。
为什么 x86 不能为一条指令使用两个显式内存操作数:
movl (mem), (mem) # AT&T syntax
mov dword [eax], [ecx] ; or the equivalent in Intel-syntax
无效,因为x86 机器代码没有an encoding for mov 和两个地址。 (事实上,没有 x86 指令可以有两种任意寻址模式。)
它有mov r32, r/m32 和mov r/m32, r32。 Reg-reg 移动可以使用mov r32, r/m32 操作码或mov r/m32, r32 操作码进行编码。许多其他指令也有两种操作码,一种是 dest 必须是寄存器,另一种是 src 必须是寄存器。
(还有一些特殊的形式,比如op r/m32, imm32,或者专门用于mov,movabs r64, [64bit-absolute-address]。)
请参阅 x86 指令集参考手册(HTML scrape;x86 tag wiki 中的其他链接)。我在这里使用了 Intel/NASM 语法,因为这是 Intel 和 AMD 的参考手册使用的。
很少有指令可以加载和存储到两个不同的地址,例如movs(字符串移动)和push/pop (mem)(What x86 instructions take two (or more) memory operands?)。在所有这些情况下,至少有一个内存地址是隐式的(由操作码隐含),而不是任意选择,可能是 [eax] 或 [edi + esi*4 + 123] 或其他。
许多 ALU 指令可用于内存目标。这是对单个内存位置的读取-修改-写入,使用相同的寻址模式进行加载然后存储。这表明限制不是 8086 无法加载和存储,而是解码复杂度(以及机器码紧凑性/格式)的限制。
没有采用两个任意有效地址的指令(即使用灵活的寻址模式指定)。 movs 具有隐式源和目标操作数,push 具有隐式目标 (esp)。
一条 x86 指令最多有一个 ModRM 字节,而一条 ModRM 只能编码一个 reg/memory 操作数(2 位用于模式,3 位用于基址寄存器)和另一个仅寄存器操作数(3 位)。使用转义码,ModRM 可以发送 SIB 字节来编码内存操作数的基数 + 缩放索引,但仍然只有编码一个内存操作数的空间。
如我上面所说,同一条指令(asm source mnemonic)的memory-source和memory-destination形式使用了两种不同的操作码。就硬件而言,它们是不同的指令.
选择这种设计的部分原因可能是实现的复杂性:如果一条指令可能需要来自 AGU(地址生成单元)的两个结果,那么必须有布线才能实现这一点。这种复杂性的一部分在于解码器确定操作码是哪条指令,并解析剩余的位/字节以找出操作数是什么。由于没有其他指令可以有多个r/m 操作数,因此需要额外的晶体管(硅区域)来支持编码两种任意寻址模式的方法。也适用于必须弄清楚一条指令有多长的逻辑,因此它知道从哪里开始解码下一条。
它还可能为指令提供五个输入依赖项(存储地址的两个寄存器寻址模式,加载地址相同,如果它是 adc 或 sbb,则为 FLAGS)。但是在设计 8086 / 80386 时,超标量 / 无序 / 依赖跟踪可能不在雷达上。 386 添加了许多新指令,因此可以完成 mov 的 mem-to-mem 编码,但没有。如果 386 已经开始将结果直接从 ALU 输出转发到 ALU 输入和类似的东西(与总是将结果提交到寄存器文件相比减少延迟),那么这个原因将是它没有实现的原因之一。
如果它存在,英特尔 P6 可能会将其解码为两个独立的微指令,一个加载和一个存储。现在引入当然没有意义,或者在 1995 年 P6 设计之后的任何时候引入,并且更简单的指令比复杂的指令获得更多的速度优势。 (请参阅http://agner.org/optimize/ 了解有关使代码快速运行的内容。)
无论如何,我看不出这很有用,至少与代码密度的成本相比没有。 如果你想要这样,你可能没有充分利用寄存器。如果可能的话,弄清楚如何在复制时动态处理你的数据。当然,有时你只需要先加载然后存储,例如在基于一个成员进行比较之后交换结构的其余部分的排序例程中。在较大的块中进行移动(例如使用 xmm 寄存器)是个好主意。
leal %esi, (%edi)
这里有两个问题:
首先,寄存器没有地址。裸露的%esi 不是有效的有效地址,因此不是lea 的有效来源
其次,lea 的目的地必须是一个寄存器。没有编码需要第二个有效地址来将目的地存储到内存中。
顺便说一句,两者都无效,因为您在两个操作数之间遗漏了,。
valid-asm.s:2: Error: number of operands mismatch for `lea'
答案的其余部分仅讨论修复该语法错误后的代码。