到目前为止我们的程序都是一条道走到黑——顺序执行。真实的程序充满岔路:if 判断、while 循环、出错重试。这一讲的六条分支指令,就是让 CPU 学会「看情况办事」的全部工具。

六条分支指令

分支 = 比较两个寄存器,成立就跳到标签处,不成立继续往下走:

指令 语义 记法
beq rs1, rs2, label 相等则跳 ==
bne rs1, rs2, label 不等则跳 !=
blt rs1, rs2, label 有符号小于 <
bge rs1, rs2, label 有符号大于等于 >=
bltu / bgeu 无符号版 <、>=

注意三个细节:

  • 没有 bltu 的「大于」「小于等于」版——交换两个操作数就是它:a > b 等价于 blt b, a。六条指令不多一条。
  • 有符号(blt/bge)和无符号(bltu/bgeu)是两条不同指令。第 02 讲 slt/sltu 的分野在这里同样适用:拿无符号指令去比「看起来是负数」的值,会得到完全相反的结论。
  • 没有无符号相等?不需要——相等判断不分符号。

RISC-V 没有 jmp

乍看指令表你会找不到无条件跳转 jmp。真相是:跳转早已藏在 jal 里——jal zero, label(跳过去,返回地址丢进 x0 = 不需要返回)。因为它太常用,汇编器给了个伪指令别名 j label。伪指令的完整家族第 07 讲盘点。

if 的汇编形状

C 代码:

if (a0 == 0) { a1 = 1; }

汇编(注意判断条件常常要反过来写——不成立就跳过 if 体):

    bne  a0, zero, skip    # a0 != 0 就跳过
    addi a1, zero, 1       # if 体
skip:

循环的两个模板

while 型(先判断再干活):

loop:
    beq  t0, t1, done      # 条件不满足就退出
    #   ... 循环体 ...
    j    loop
done:

for 型(计数循环:1 加到 100):

    addi t0, zero, 1       # i = 1
    addi t1, zero, 0       # sum = 0
for:
    bgt  t0, t2, done      # i > 100 退出(bgt 是 bge 换位的伪指令)
    add  t1, t1, t0        # sum += i
    addi t0, t0, 1         # i++
    j    for
done:

跑完 t1 = 5050。高斯小时候口算的题,CPU 做了 100 次循环——但它只需 3 纳秒。

跳转目标其实是「偏移量」

汇编里写标签很直观,但机器码里标签会被换算成相对当前 PC 的偏移,编码进指令的立即数字段(12 位,±4KB 范围,且必须是 2 的倍数)。所以:

  • 分支只能在小范围内跳(跨太远要借寄存器中转,编译器比你急)
  • 第 08 讲拆指令编码时,你会看到这个立即数在 32 位里排列顺序是打乱的——那是为了硬件复用,先知道有这回事

动手验证

把「1 加到 100」的代码敲进模拟器,单步执行,观察 t0 递增、t1 累积。然后做个小挑战:改成「只加偶数」(提示:andi t3, t0, 1 判断奇偶——第 06 讲正好讲位操作)。

免费下载 HI RISC-V App,关卡判题帮你验证对错。

本讲三句话

  1. 六条分支指令 beq/bne/blt/bge/bltu/bgeu,比较两个寄存器决定跳不跳
  2. 无条件跳转是伪指令 j,本质是 jal zero, label
  3. 分支立即数 = 相对 PC 的偏移,范围 ±4KB——循环体别写太大

下一讲是重头戏:函数调用。ra、栈帧、调用约定——汇编的成人礼。