跳转至

算术

与许多高级语言一样,汇编语言有两种算术:一元和二元。一元指令接受单个操作数,它同时充当目标和源。这个操作数可以是寄存器或内存地址。表 4-7 显示了 x86 中常见的一元算术指令。

指令语法结果给操作数值加 1。从操作数值减 1。对操作数值做逻辑非(翻转所有位)。执行二进制补码取负(翻转所有位并加 1;本质上乘以 −1)。

另一方面,二元指令(构成 x86 算术的大多数)在语法上与 MOV 指令相似。它们需要两个操作数,并有类似的操作数限制。与 MOV 不同,它们的目标操作数有第二个用途:它也是计算中的左侧值。例如,汇编操作 ADD EAX,EBX 等价于 C++ 中的 EAX = EAX + EBX 或 EAX += EBX。表 4-8 显示了常见的 x86 二元算术指令。

指令语法功能操作数说明 destination += source destination -= source destination &= source destination |= source destination ^= source destination = destination << source 必 source须是 CL 或 8 位立即值。destination = destination >> source 必 source须是 CL 或 8 位立即值。destination *= sourcedestinatio n 必须是寄存器;s ource 不能是立即值。

在这些算术指令中,IMUL 很特殊,因为你可以给它传第三个操作数,形式是立即值。用这个原型,目标操作数不再参与计算,计算发生在其余操作数之间。例如,汇编命令 IMUL EAX,EBX,4h 等价于 C++ 中的 EAX = EBX * 0x4。

你也可以给 IMUL 传单个操作数。这种情况下,操作数充当源,可以是内存地址或寄存器。根据源操作数的大小,指令会用 EAX 寄存器的不同部分作为输入和输出,如表 4-9 所示。

源大小输入输出 8 位16 位,存储在 AH:AL(即 AX)中 16 位32 位,存储在 DX:AX 中(位 0–15 在 AX,位 16–31 在 DX)32 位64 位,存储在 EDX:EAX 中(位 0–31 在 EAX,位 32–63 在 EDX)注意,即使输入只是一个寄存器,每个输出都使用两个寄存器。这是因为在乘法中,结果通常比输入大。

让我们看一个使用单个 32 位操作数的 IMUL 计算示例:

IMUL [BADFOODh] ; 32 位操作数位于地址 0xBADFOOD

这个命令的行为类似下面的伪代码:

EDX:EAX = EAX * [BADFOODh]

类似地,这是一个使用单个 16 位操作数的操作:

IMUL CX ; 16 位操作数存储在 CX 中

它对应的伪代码:

DX:AX = AX * CX

最后,这是单个 8 位操作数的 IMUL 命令:

IMUL CL ; 8 位操作数存储在 CL 中

它对应的伪代码:

AX = AL * CL

x86 汇编语言也有除法,通过 IDIV 指令。IDIV 指令接受单个源操作数,遵循与 IMUL 类似的寄存器规则。如表 4-10 所示,IDIV 操作需要两个输入和两个输出。

源大小输入输出 8 位余数在 AH;商在 AL 16 位余数在 DX;商在 AX 32 位余数在 EDX;商在 EAX

在除法中,输入通常比输出大,所以输入占两个寄存器。此外,除法操作必须存储余数,余数存储在第一个输入寄存器中。例如,32 位 IDIV 计算看起来像这样:

MOV EDX, 0 ; 输入中没有高序 DWORD所以 EDX  0
MOV EAX, inputValue ; 32 位输入值
IDIV ECX ;  ECX  EDX:EAX

以及表达底层情况的伪代码:

EDX = EDX:EAX % ECX ; 余数

IDIV 和 IMUL 的这些细节很重要,否则仅看命令时行为可能会非常模糊。