前几讲我们用汇编做「算术」,这一讲开始做「手术」:直接对 32 个二进制位做精细操作。底层开发的一半工作——寄存器配置、协议封包、硬件控制——都是位操作。好消息是,全部工具只有六条指令。

三条逻辑指令:and、or、xor

指令 寄存器版 立即数版 用途口诀
AND and rd, rs1, rs2 andi rd, rs1, imm 留:1 的位保留,0 的位清零
OR or rd, rs1, rs2 ori rd, rs1, imm 置:1 的位置 1,0 的位保留
XOR xor rd, rs1, rs2 xori rd, rs1, imm 翻:1 的位取反,0 的位保留

三个立等可用的例子:

andi a0, a0, 0xF      # 取低 4 位(掩码 0b1111)—— 留
ori   a0, a0, 0x80    # 把第 7 位置 1 —— 置
xori  a0, a0, 0xFF    # 低 8 位全部翻转 —— 翻

再记两个「伪指令的真相」:not a0, a1 是 xori a0, a1, -1(与全 1 异或 = 全取反);neg a0, a1 是 sub a0, zero, a1(0 减它 = 取负)。逻辑和算术在这里握手。

移位:乘除 2 的快捷键

指令 语义 用途
slli rd, rs1, shamt 左移 shamt 位,低位补 0 ×2ⁿ
srli 逻辑右移:高位补 0 无符号 ÷2ⁿ
srai 算术右移:高位补符号位 有符号 ÷2ⁿ

(sll/srl/sra 是寄存器版,移位量来自寄存器低 5 位。)

srl 和 sra 的区别只在负数上:0x80000000 右移 1 位,逻辑版得 0x40000000(补 0,当成大正数),算术版得 0xC0000000(补符号位 1,保持负数身份)。处理有符号数想除以 2,必须 srai。

移位还是「定位」的高手:把第 n 位推到符号位做判断、把字段对齐到边界拼包——协议代码里全是它。

位操作四件套:置 1、清 0、翻转、测试

操作「第 3 位」的全部套路(BIT = 1 << 3):

ori   a0, a0, 8       # 置 1:  a0 |= 0b1000
andi  a0, a0, -9      # 清 0:  a0 &= ~0b1000(-9 = 0xFFFFFFF7)
xori  a0, a0, 8       # 翻转:a0 ^= 0b1000
srli  a0, a0, 3
andi  a0, a0, 1       # 测试:移到最低位再 and,得 0/1

这套动作你其实天天在用——每次 C 里写 reg |= (1 << 7) 配置硬件寄存器,编译器生成的就是 ori。Linux 内核的 set_bit/clear_bit 也由此而来。

彩蛋:不用临时变量交换两数

xor  a0, a0, a1       # a0 = a^b
xor  a1, a0, a1       # a1 = a^b^b = a
xor  a0, a0, a1       # a0 = a^b^a = b

三次异或完成交换。实战中它并不比用一个临时寄存器更快(现代 CPU 有寄存器重命名),但它是理解 xor「可逆性」最好的练习——密码学里的流加密用的正是同一个性质。

动手验证

在模拟器里做这个小实验:addi t0, zero, -1 后分别 srli t1, t0, 28 和 srai t2, t0, 28——看 t1 变成 0xF、t2 保持 0xF;再把初值换成 0x80000000 重复,差异立刻现形(一个 0,一个 0xF)。

免费下载 HI RISC-V App,寄存器面板看二进制,位操作一目了然。

本讲三句话

  1. AND 留、OR 置、XOR 翻——掩码思维是位操作的全部心法
  2. 右移分逻辑(补 0)与算术(补符号位):负数除以 2 必须 srai
  3. 置 1 用 or、清 0 用 and、翻转用 xor、测试用移位加 and

下一讲解决一个遗留问题:立即数最大只有 ±2048,那 10 亿这样的大常数怎么装进寄存器?