Appearance
B 指令与条件后缀:让 CPU 学会做选择

上一课的 CMP 是侦察兵——减一下、留好证据(标志位),然后退场。这一课,决策者终于登场:B 指令和它的条件后缀家族。有了它们,程序不再是从第一行跑到最后一行的直线,而是第一次拥有了"岔路"。
1. 从直线到岔路:什么是分支
到目前为止,我们写的所有程序都是"顺序流":PC(程序计数器)自顾自地每次 +4,一条接一条往下执行,谁也不拦它。而分支指令做的事情只有一件——按条件决定 PC 的下一个值,而不是无脑 +4。

图 1 · 顺序流 vs 分支流:程序第一次有了岔路
分支需要两个新角色配合:
- 标签(label):
cond1:、cond2:这样以冒号结尾的行,本身不占指令,只是给某个地址起了个名字 - 跳转:
b cond2表示"下一条指令去 cond2 那里取"——汇编器会把标签换算成地址差,编进指令里
asm
b cond2 @ 无条件跳到 cond2
cond1:
mov r2, #1 @ 标签只是地址的名字,不产生指令
cond2:
mov r3, #22. 无条件分支 B:直接改写 PC
B(Branch,分支)是最朴素的一条:不问任何条件,直接跳。它本质上就是把 PC 改成目标标签的地址——流水线里的下一条指令,从标签处重新开始取。
注: 在反汇编工具(如 Ghidra)里,你可能会看到
BAL这种写法。AL 是"总是"(ALways)的意思,BAL 和 B 是同一条指令的两种写法——"总是跳"就是"无条件跳"。
3. 条件后缀:BEQ 看 Z 行事
B 的真正威力在于后缀。把两个字母的条件码直接拼在 B 后面,它就长出了判断力:
beq:B + EQ(equal,相等)——Z=1 时才跳bne:B + NE(not equal,不等)——Z=0 时才跳blt:B + LT(less than,小于)——按 N 和 V 的组合判断
回忆上一课:cmp r0, r1 之后,两数相等时 Z=1。于是"相等则跳"的完整拼图就是:
asm
cmp r0, r1 @ 侦察兵:相等则 Z=1
beq cond1 @ 决策者:Z=1 才跳去 cond1这就是 ARM 世界里 if 语句的最小单元。CMP 布置标志,条件分支看标志行事——上一课埋的伏笔,在这一课全部兑现。
4. 完整实验:BEQ 不跳,B 兜底
看视频里的单步实验(GDB + GEF),R0=4、R1=5,两数不相等:

图 2 · 单步实验:BEQ 不跳,B 兜底跳(R0=4, R1=5)
asm
mov r0, #4
mov r1, #5
cmp r0, r1 @ 4 ≠ 5,Z=0
beq cond1 @ Z=0,不跳,继续往下
b cond2 @ 无条件跳:直接着陆 cond2
cond1:
mov r2, #1 @ 被跳过,R2 始终是 0
cond2:
mov r3, #2 @ 跳转着陆点,R3 = 2调试器里单步走完,R2=0、R3=2——cond1 分支被整个跳过。如果把 R0 也改成 5,Z=1,BEQ 就会跳进 cond1,R2=1。同一份代码,标志位不同,走的路就不同。
5. 顺序穿透:最烂的 if 语句
视频作者毫不客气地把刚才的代码称为"世界上最烂的 if 语句"。为什么?因为它有个暗伤:汇编里没有"块"的概念,标签挡不住顺序执行。
假如 R0=R1,BEQ 跳进 cond1,mov r2, #1 执行完——然后呢?PC 继续 +4,顺势滑进了 cond2,mov r3, #2 也执行了!两个分支都跑了一遍,else 形同虚设。
易错点: 高级语言的 if-else 自带"互斥",汇编里互斥要靠你自己用跳转搭出来。忘记加"防穿透"分支,是条件分支新手最经典的 bug。
正确的拼装需要两条"胶水分支":

图 4 · if-else 的正确拼装:两条「胶水分支」缺一不可
asm
cmp r0, r1
beq cond1 @ 胶水① 条件跳:相等才去 if 体
b cond2 @ 胶水② 兜底跳:不等就去 else 体
cond1:
mov r2, #1 @ if 体
b end @ 胶水③ 防穿透:干完就走
cond2:
mov r3, #2 @ else 体
end:三条 B 家族指令各司其职:一条管"选谁",一条管"躲开谁",一条管"收队"。看懂这个三段式,你就看懂了所有 if-else 的汇编骨架。
6. 条件后缀大家族
EQ 和 NE 只是开始。B 后面可以拼一整族后缀,全部围绕 CPSR 标志设计:

图 3 · 条件后缀速查表:B 的七十二变
| 后缀 | 含义 | 何时跳转 |
|---|---|---|
| EQ | 相等 | Z = 1 |
| NE | 不等 | Z = 0 |
| GT | 大于 | Z = 0 且 N = V |
| LT | 小于 | N ≠ V |
| GE | 大于等于 | N = V |
| LE | 小于等于 | Z = 1 或 N ≠ V |
| AL | 总是 | 无条件(通常省略) |
注: GT/LT/GE/LE 看的是 N 和 V 的组合而不只是 N,是为了正确处理有符号溢出——这正是 V 标志存在的意义。好消息是你不用背组合规则,记住"后缀语义 = 高级语言的比较符"就够了。
有了这一族后缀,if (a > b)、while (i != 0)、for 循环……高级语言里的所有控制流,都能拆成 CMP + 条件分支的组合。接下来的课程就会把 C 语言的 if 和循环逐个翻译成汇编,看看编译器到底干了什么。
小结
- B 指令:无条件改写 PC,跳到标签处;标签只是地址的名字,不产生指令
- 条件后缀:拼在 B 后面形成 BEQ、BNE、BLT……按 CPSR 标志决定是否跳
- 组合拳:CMP 布置标志 + 条件分支看标志行事,这是所有判断的汇编原型
- 顺序穿透:标签挡不住顺序执行,if 体末尾必须加
b end防穿透 - 后缀家族:EQ/NE/GT/LT/GE/LE/AL,语义对应高级语言的比较符;AL 即无条件 B 本身
- 下集预告:把 C 语言的 if 和循环丢进编译器,逆向看它们变成什么样的 CMP + B 组合
到这里,CPU 终于学会了做选择。数据会搬了(MOV/LDR/STR),会算了(ADD/SUB/MUL),会比了(CMP),现在还会跳了(B 家族)——图灵机的最后一块拼图就位。下一课见。