Skip to content

乘法

在汇编语言中,乘法操作比加法复杂一些,因为两个 \(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. 示例

mov al, 0xFF    ; 255
mov bl, 2
mul bl          ; AX = 255 * 2 = 510 (0x01FE)
                ; 此时 AL=0xFE, AH=0x01

二、 IMUL (Signed Multiply) —— 有符号乘法

IMUL 比 MUL 强大且灵活得多。它不仅支持隐式乘法,还支持像 add 那样的多操作数格式。

1. 格式一:单操作数(与 MUL 类似)

这种格式下,规则与 MUL 完全一致(使用 RDX:RAX 这种组合),但它会处理符号位(补码)。

mov rax, -2
mov rbx, 5
imul rbx        ; RAX = -10,RDX 会进行符号扩展(全为1)

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,你的除法就会算出极其巨大的、错误的结果。


五、 常见坑点

  1. 忘记 RDX 被覆盖: 即使你觉得乘法结果很小,mul 或 imul(单操作数格式)依然会强制清空或修改 RDX。如果 RDX 里之前存了重要的地址或数据,它就丢了。

    • 建议:如果不需要 128 位结果,尽量使用 imul rax, rbx(双操作数格式),因为它不影响 RDX。
  2. 有符号还是无符号?

    • 如果你在算内存地址、数组索引,通常用 MUL。
    • 如果你在算数学题、温度、坐标,通常用 IMUL。

六、 总结对比

特性 MUL IMUL
数据类型 无符号 (0, 1, 2...) 有符号 (-1, 0, 1...)
操作数数量 只能有 1 个 1, 2, 或 3 个
高位寄存器 总是修改 (如 RDX) 单操作数修改,多操作数不修改
性能 较快 现代 CPU 上两者几乎一样快

小练习: 如果你执行以下指令,RAX 的值是多少?

mov rax, 0xFFFFFFFFFFFFFFFF ; 即有符号数 -1
mov rbx, 2
mul rbx   ; 无符号乘法
提示:此时 RAX 是被当作 2的64次方-1 来算的,结果会溢出到 RDX 吗?