前几讲我们用汇编做「算术」,这一讲开始做「手术」:直接对 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,寄存器面板看二进制,位操作一目了然。
本讲三句话
- AND 留、OR 置、XOR 翻——掩码思维是位操作的全部心法
- 右移分逻辑(补 0)与算术(补符号位):负数除以 2 必须
srai - 置 1 用 or、清 0 用 and、翻转用 xor、测试用移位加 and
下一讲解决一个遗留问题:立即数最大只有 ±2048,那 10 亿这样的大常数怎么装进寄存器?