跳转到内容

x86 汇编/算术

来自维基教科书,自由的教科书

所有算术指令都在(一个)ALU中执行。 ALU 只能执行整数运算,对于浮点运算指令,请参见“浮点”章节

基本运算

[编辑 | 编辑源代码]

算术指令有两个操作数:目标和源。

  • 目标必须是寄存器或内存位置。
  • 可以是内存位置、寄存器或常数值。

注意,最多只有一个操作数可以是内存位置。

加法和减法

[编辑 | 编辑源代码]
add addend, destination GAS 语法
add destination, addend Intel 语法

这将 addend 添加到 destination,并将结果存储在 destination 中。


sub subtrahend, destination GAS 语法
sub destination, subtrahend Intel 语法

add 相似,只是它从 destination 中减去 subtrahend。 在 C 中:destination -= subtrahend;

无符号乘法

[编辑 | 编辑源代码]

mul multiplicand

这将 multiplicand 乘以累加器中相应字节长度的值。

multiplicand 的宽度 1 字节 2 字节 4 字节 8 字节
相应的 multiplier AL AX EAX RAX
product 高位部分存储在 AH DX EDX RDX
product 低位部分存储在 AL AX EAX RAX
mul 使用的结果寄存器

在第二种情况下,目标不是 EAX,这是为了与为旧处理器编写的代码向后兼容。

受影响的标志是

  • OF ≔ product 的高位部分 ≠ 0
  • CF ≔ product 的高位部分 ≠ 0

所有其他标志都未定义。

有符号乘法

[编辑 | 编辑源代码]

imul multiplicand

此指令几乎与 mul 相同,但它对符号位(MSB)的处理不同。

imul 指令还接受其他两种格式


imul multiplicand, destination GAS 语法
imul destination, multiplicand Intel 语法

这将 destination 乘以 multiplicand,并将结果(乘积)放入 destination 中。


imul multiplicand, multiplier, product GAS 语法
imul product, multiplier, multiplicand Intel 语法

这将 multiplier 乘以 multiplicand 并将其放入 product 中。

div divisor

这将被除数寄存器中的值除以 divisor,见下表。


idiv arg

div 相同,只是有符号。

divisor 的宽度 1 字节 2 字节 4 字节 8 字节
被除数 AX DX AX EDX EAX RDX RAX
remainder 存储在 AH DX EDX RDX
quotient 存储在 AL AX EAX RAX
div 的结果寄存器

圆圈()表示串联。 对于除数大小为 4,这意味着 EDX 是输入数字的位 32-63,EAX 是位 0-31(低位数字的权重较低,在本例中)。

由于通常对于有符号除法,输入值通常是 32 位或 64 位,因此您通常需要使用CDQCQO 在除法之前将 EAX 符号扩展到 EDXRAXRDX

如果商不能放入商寄存器,则会发生算术溢出中断。 运算后,所有标志都处于未定义状态。

符号反转

[编辑 | 编辑源代码]

neg arg

算术否定参数(即二进制补码否定)。

进位算术指令

[编辑 | 编辑源代码]
adc src, dest GAS 语法
adc dest, src Intel 语法


加带进位。 将 src + CF 添加到 dest,将结果存储在 dest 中。 通常在正常加法指令之后使用,以处理比寄存器大小大两倍的值。 在以下示例中,source 包含一个 64 位数字,它将被添加到 destination 中。

mov eax, [source] ; read low 32 bits
mov edx, [source+4] ; read high 32 bits
add [destination], eax ; add low 32 bits
adc [destination+4], edx ; add high 32 bits, plus carry


sbb src, dest GAS 语法
sbb dest, src Intel 语法

减带借位。 从 dest 中减去 src + CF,将结果存储在 dest 中。 通常在正常减法指令之后使用,以处理比寄存器大小大两倍的值。

增量和减量

[编辑 | 编辑源代码]

inc augend

此指令将寄存器值 augend 增加 1。 它执行速度比 add arg, 1 快得多,但它不会影响 CF

dec 被减数

被减数 中的值减 1,但这比语义上等效的 sub 被减数, 1 快得多。

操作数

[编辑 | 编辑源代码]

被减数 可以是寄存器或内存操作数。

  • 一些编程语言使用所有位为零或所有位设置为 1 来表示布尔值。当您编写布尔函数时,需要考虑这一点。 dec 指令可以帮助您做到这一点。通常,您根据标志设置最终的(布尔)结果。通过选择一个与预期相反的指令,然后递减结果值,您将获得满足编程语言要求的值。以下是一个测试零的简单示例。
    xor rax, rax   ; rax ≔ false (ensure result is not wrong due to any residue)
    test rdi, rdi  ; rdi ≟ 0 (ZF ≔ rax = 0)
    setnz al       ;  al ≔ ¬ZF
    dec rax        ; rax ≔ rax − 1
    
    如果您打算设置false,则“错误”设置的 1 将通过 dec “修复”。如果您打算设置true,则表示为 -1,您将递减值为零,它的“下溢”将导致所有位翻转。注意,一些架构执行 dec 缓慢,因为标志寄存器仅被部分覆盖。因此,使用 neg 通常更有效
    setz al        ;  al ≔ ZF
    neg rax        ; rax ≔ 0 − rax
    
    ,尽管它也会影响 CF
  • 由于 incdec 不会影响 CF,您可以使用这些指令来更新循环的计数变量,而不会覆盖其中存储的一些信息。如果您需要一个不影响任何标志的指令,同时隐式地执行 dec,您可以使用相当慢的 loop

指针算术

[编辑 | 编辑源代码]

lea 指令可用于算术运算,尤其是在指针上。请参阅 “数据传输”一章,§“加载有效地址”

华夏公益教科书