3.7 算术运算指令
x86-64 提供了丰富的算术和逻辑运算指令,包括加法、减法、乘法、位运算和移位操作。
双操作数算术指令
这些指令有两个操作数:源操作数(S)和目的操作数(D),结果写回目的操作数。
| 指令 | 含义 | C 语言等价 | 示例 |
|---|---|---|---|
addq S, D | D = D + S | D += S | addq %rax, %rbx |
subq S, D | D = D - S | D -= S | subq $8, %rsp |
imulq S, D | D = D × S | D *= S | imulq %rcx, %rax |
andq S, D | D = D & S | D &= S | andq $0xFF, %rax |
orq S, D | D = D | S | D |= S | orq %rdx, %rax |
xorq S, D | D = D ^ S | D ^= S | xorq %rax, %rax |
跳转指令与条件码
这些指令的另一个重要作用是**设置 **,为后续条件跳转提供依据。
清零惯用法
xorq %rax, %rax # rax = rax ^ rax = 0 (最快速的寄存器清零)
# 注意:更常见的是 xorl %eax, %eax(32 位操作,自动清零高 32 位)单操作数算术指令
这些指令只有一个操作数,既是源也是目的。
| 指令 | 含义 | C 语言等价 | 示例 |
|---|---|---|---|
incq D | D = D + 1 | D++ | incq %rax |
decq D | D = D - 1 | D-- | decq (%rdi) |
negq D | D = -D | D = -D | negq %rax |
notq D | D = ~D | D = ~D | notq %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, D | D += S |
| 减法 | subq S, D | D -= S |
| 乘法 | imulq S, D | D *= S(低 64 位) |
| 自增 | incq D | D++ |
| 自减 | decq D | D-- |
| 取负 | negq D | D = -D |
| 取反 | notq D | D = ~D |
| 与 | andq S, D | D &= S |
| 或 | orq S, D | D |= S |
| 异或 | xorq S, D | D ^= S |
| 左移 | salq/shLq k, D | D <<= k |
| 算术右移 | sarq k, D | D >>= k(符号扩展) |
| 逻辑右移 | shrq k, D | D >>= k(零扩展) |
相关笔记
-
- 用 lea 替代乘法运算
-
- 算术指令对条件码的影响
-
- 除法指令与对齐的关系