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
|
在第二种情况下,目标不是 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
|
圆圈(○)表示串联。 对于除数大小为 4,这意味着 EDX
是输入数字的位 32-63,EAX
是位 0-31(低位数字的权重较低,在本例中)。
由于通常对于有符号除法,输入值通常是 32 位或 64 位,因此您通常需要使用CDQ 或CQO 在除法之前将 EAX
符号扩展到 EDX
或 RAX
到 RDX
。
如果商不能放入商寄存器,则会发生算术溢出中断。 运算后,所有标志都处于未定义状态。
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
指令可以帮助您做到这一点。通常,您根据标志设置最终的(布尔)结果。通过选择一个与预期相反的指令,然后递减结果值,您将获得满足编程语言要求的值。以下是一个测试零的简单示例。如果您打算设置false,则“错误”设置的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
1
将通过dec
“修复”。如果您打算设置true,则表示为 -1,您将递减值为零,它的“下溢”将导致所有位翻转。注意,一些架构执行dec
缓慢,因为标志寄存器仅被部分覆盖。因此,使用neg
通常更有效,尽管它也会影响 CF。setz al ; al ≔ ZF neg rax ; rax ≔ 0 − rax
- 由于
inc
和dec
不会影响 CF,您可以使用这些指令来更新循环的计数变量,而不会覆盖其中存储的一些信息。如果您需要一个不影响任何标志的指令,同时隐式地执行dec
,您可以使用相当慢的loop
。
lea
指令可用于算术运算,尤其是在指针上。请参阅 “数据传输”一章,§“加载有效地址”。