3.7 算术运算指令

x86-64 提供了丰富的算术和逻辑运算指令,包括加法、减法、乘法、位运算和移位操作。

双操作数算术指令

这些指令有两个操作数:源操作数(S)和目的操作数(D),结果写回目的操作数。

指令含义C 语言等价示例
addq S, DD = D + SD += Saddq %rax, %rbx
subq S, DD = D - SD -= Ssubq $8, %rsp
imulq S, DD = D × SD *= Simulq %rcx, %rax
andq S, DD = D & SD &= Sandq $0xFF, %rax
orq S, DD = D | SD |= Sorq %rdx, %rax
xorq S, DD = D ^ SD ^= Sxorq %rax, %rax

跳转指令与条件码

这些指令的另一个重要作用是**设置 **,为后续条件跳转提供依据。

清零惯用法

xorq    %rax, %rax             # rax = rax ^ rax = 0 (最快速的寄存器清零)
# 注意:更常见的是 xorl %eax, %eax(32 位操作,自动清零高 32 位)

单操作数算术指令

这些指令只有一个操作数,既是源也是目的。

指令含义C 语言等价示例
incq DD = D + 1D++incq %rax
decq DD = D - 1D--decq (%rdi)
negq DD = -DD = -Dnegq %rax
notq DD = ~DD = ~Dnotq %rax
# 单操作数指令的应用
incq    16(%rbp)               # 栈上局部变量自增
decq    (%rdi)                 # 指针指向的值自减
negq    %rax                   # 取反(求补码)
notq    %rax                   # 按位取反

移位指令

移位指令的移位数可以来自立即数或 %cl 寄存器。

指令含义C 等价
salq k, D算术左移D <<= k
shlq k, D逻辑左移(与 salq 相同)D <<= k
sarq k, D算术右移(符号位填充)D >>= k(有符号)
shrq k, D逻辑右移(0 填充)D >>= k(无符号)
# 移位操作示例
salq    $3, %rax               # rax = rax << 3 (乘以 8)
sarq    $2, %rdi               # rdi = rdi >> 2 (有符号除以 4)
shrq    $4, %rsi               # rsi = rsi >> 4 (无符号除以 16)
 
# 使用 %cl 作为移位数
movb    $5, %cl                # cl = 5
shlq    %cl, %rbx              # rbx = rbx << 5

移位数限制

  • 移位计数只能是立即数(0~63)或寄存器 %cl 的低 6 位
  • 不允许使用其他寄存器作为移位数
# 正确
shlq    $4, %rax               # 立即数移位
 
# 正确(但使用 %cl)
movb    $4, %cl
shlq    %cl, %rax
 
# 错误:不能使用 %rdx 作为移位数
# shlq %rdx, %rax     ❌

乘法的特殊指令

64 位乘法:imulq

imulq S, D 执行 64 位乘法,结果截断到 64 位(只保留低 64 位)。

128 位乘法:mulq / imulq(单操作数)

# 无符号 64 位乘法,结果 128 位
mulq    %rdi                # rdx:rax = rax * rdi (无符号)
 
# 有符号 64 位乘法,结果 128 位
imulq   %rdi                # rdx:rax = rax * rdi (有符号)
  • 操作数:%rax × 源操作数
  • 结果高 64 位存入 %rdx,低 64 位存入 %rax

除法指令

# 有符号除法
cqto                        # 将 %rax 符号扩展到 %rdx:%rax (准备被除数)
idivq   %rdi                # %rax = 商, %rdx = 余数 (有符号)
 
# 无符号除法
movq    $0, %rdx            # 清零 %rdx (准备被除数)
divq    %rdi                # %rax = 商, %rdx = 余数 (无符号)
  • 被除数:%rdx:%rax(128 位)
  • 除数:源操作数
  • 商 → %rax,余数 → %rdx

乘除法的移位优化

编译器对乘以/除以 2 的幂进行优化:

乘以 2 的幂

// C 代码
long mul_by_8(long x) {
    return x * 8;
}
# 编译器生成的汇编(移位优化)
mul_by_8:
    salq    $3, %rdi            # rax = x << 3
    movq    %rdi, %rax
    ret

除以 2 的幂(无符号)

// C 代码
unsigned long div_by_8(unsigned long x) {
    return x / 8;
}
# 编译器生成的汇编(移位优化)
div_by_8:
    shrq    $3, %rdi            # rdi = x >> 3 (逻辑右移)
    movq    %rdi, %rax
    ret

除以 2 的幂(有符号,需修正)

// C 代码
long div_by_8(long x) {
    return x / 8;
}

有符号除法不能直接用算术右移,因为负数向右移会向下取整(而 C 要求向零取整)。编译器需要修正

# 编译器生成的汇编(有符号除以 8)
div_by_8:
    testq   %rdi, %rdi          # 检查符号
    js      negative            # 如果是负数,需要修正
    sarq    $3, %rdi            # 正数:算术右移 3 位
    movq    %rdi, %rax
    ret
negative:
    addq    $7, %rdi            # 修正:(x + 7) >> 3
    sarq    $3, %rdi
    movq    %rdi, %rax
    ret

修正原理:对于负数 等价于 (算术右移)。

算术指令总结表

类别指令操作
加法addq S, DD += S
减法subq S, DD -= S
乘法imulq S, DD *= S(低 64 位)
自增incq DD++
自减decq DD--
取负negq DD = -D
取反notq DD = ~D
andq S, DD &= S
orq S, DD |= S
异或xorq S, DD ^= S
左移salq/shLq k, DD <<= k
算术右移sarq k, DD >>= k(符号扩展)
逻辑右移shrq k, DD >>= k(零扩展)

相关笔记

    • 用 lea 替代乘法运算
    • 算术指令对条件码的影响
    • 除法指令与对齐的关系

链接到