乘法
在汇编语言中,乘法操作比加法复杂一些,因为两个 \(n\) 位数相乘,结果可能会达到 \(2n\) 位。为了处理这个变大的结果,CPU 采用了特定的寄存器组合。
MUL 和 IMUL 的核心区别在于:MUL 处理无符号数,而 IMUL 处理有符号数。
一、 MUL (Unsigned Multiply) —— 无符号乘法
MUL 指令只接受一个操作数(乘数),而另一个乘数(被乘数)则是隐式地存储在累加器(AL/AX/EAX/RAX)中。
1. 寄存器对应关系(固定规则)
由于结果可能很大,CPU 会使用两个寄存器来存放结果的高位和低位。
| 乘数大小 | 被乘数 (固定) | 结果存放位置 | 示例 |
|---|---|---|---|
| 8 位 | AL |
AX (总共16位) |
mul bl ; AX = AL * BL |
| 16 位 | AX |
DX:AX (高16位在DX,低16位在AX) |
mul bx |
| 32 位 | EAX |
EDX:EAX (高32位在EDX,低32位在EAX) |
mul ebx |
| 64 位 | RAX |
RDX:RAX (高64位在RDX,低64位在RAX) |
mul rbx |
2. 示例
二、 IMUL (Signed Multiply) —— 有符号乘法
IMUL 比 MUL 强大且灵活得多。它不仅支持隐式乘法,还支持像 add 那样的多操作数格式。
1. 格式一:单操作数(与 MUL 类似)
这种格式下,规则与 MUL 完全一致(使用 RDX:RAX 这种组合),但它会处理符号位(补码)。
2. 格式二:双操作数(最常用)
这种格式不会使用 RDX,结果直接存入第一个寄存器。注意:如果结果超过了目标寄存器的长度,高位会被丢弃。
* 语法:IMUL 寄存器, 寄存器/内存
* 例子:imul rax, rbx ; RAX = RAX * RBX
3. 格式三:三操作数
允许你将两个数相乘,并将结果存入第三个寄存器。通常其中一个是立即数。
* 语法:IMUL 寄存器, 寄存器/内存, 立即数
* 例子:imul rax, rbx, 10 ; RAX = RBX * 10
三、 乘法对标志位的影响(CF 和 OF)
对于乘法,我们主要关注 CF (进位) 和 OF (溢出)。这两个标志位在乘法里的逻辑是一样的:
- 对于
MUL:如果结果的高半部分(如RDX或AH)不是 0,则CF=1, OF=1。这表示结果太大了,低半部分放不下。 - 对于
IMUL:如果结果的高半部分不是低半部分的符号扩展(即高位不全是 0 或全是不 1),则CF=1, OF=1。这同样表示发生了溢出。
四、 重点:RDX:RAX 这种双寄存器结构
在 64 位汇编中,当你执行 mul rbx 或 imul rbx(单操作数)时,结果是一个 128 位 的数:
* 低 64 位 存入 RAX。
* 高 64 位 存入 RDX。
为什么这很重要?
如果你在做乘法之后紧接着要做除法(DIV),除法指令默认就会寻找 RDX:RAX 作为被除数。如果你不小心修改了 RDX,你的除法就会算出极其巨大的、错误的结果。
五、 常见坑点
-
忘记 RDX 被覆盖: 即使你觉得乘法结果很小,
mul或imul(单操作数格式)依然会强制清空或修改 RDX。如果RDX里之前存了重要的地址或数据,它就丢了。- 建议:如果不需要 128 位结果,尽量使用
imul rax, rbx(双操作数格式),因为它不影响RDX。
- 建议:如果不需要 128 位结果,尽量使用
-
有符号还是无符号?
- 如果你在算内存地址、数组索引,通常用
MUL。 - 如果你在算数学题、温度、坐标,通常用
IMUL。
- 如果你在算内存地址、数组索引,通常用
六、 总结对比
| 特性 | MUL | IMUL |
|---|---|---|
| 数据类型 | 无符号 (0, 1, 2...) | 有符号 (-1, 0, 1...) |
| 操作数数量 | 只能有 1 个 | 1, 2, 或 3 个 |
| 高位寄存器 | 总是修改 (如 RDX) | 单操作数修改,多操作数不修改 |
| 性能 | 较快 | 现代 CPU 上两者几乎一样快 |
小练习: 如果你执行以下指令,RAX 的值是多少?
提示:此时 RAX 是被当作 2的64次方-1 来算的,结果会溢出到 RDX 吗?