Appearance
CMP 比较指令:一场只留痕迹的减法

上一课的移位与旋转为位操作家族收了官。从这一课开始,我们向真正的"复杂指令"进发——目标是让 CPU 学会做选择(分支)。但在做选择之前,得先解决一个前置问题:CPU 怎么知道两个数谁大谁小、是否相等?
答案就是今天的主角 CMP。它是整个分支体系的侦察兵:所有"如果……就……"的判断,都要先靠它探明敌情。
1. 比较的本质:减一下就知道
想知道 R0 和 R1 谁大?小学数学就够了:做个减法,看结果的正负零。
- 差是负数 → R0 比 R1 小
- 差是正数 → R0 比 R1 大
- 差是零 → 两个数相等
CMP 干的就是这件事。cmp r0, r1 会在 CPU 内部暗中计算 R0 - R1,然后根据差值刷新 CPSR 的标志位。比较的全部信息,就藏在这几个标志里。

图 1 · CMP 的本质:只留痕迹、不留结果的减法
2. 只留痕迹:结果丢弃,标志更新
注意 CMP 和第二课 SUB 的关键区别:它不保存结果。
sub r2, r0, r1 会把差值写进 R2;而 cmp r0, r1 算完就扔——没有任何寄存器被修改,R0、R1 原封不动。它唯一留下的痕迹,是 CPSR 高 4 位的标志被刷新。
注: 这就是为什么 CMP 的语法里只有两个操作数,没有目标寄存器。可以理解为:cmp r0, r1 ≡ 一条"扔掉结果的 subs r0, r1"。
为什么设计得这么"浪费"?因为做判断时我们根本不关心差值是几,只关心它是正、是负还是零。差值留着反而占寄存器,不如直接丢弃——这就是"只比较、不计算"的专用指令。
3. CPSR 高 4 位:CMP 的记分牌
第二课我们见过 CPSR 的 N、Z、C、V 四个标志位,当时它们是为 ADD/SUB 服务的。在 CMP 的语境下,这四个位有了新的读法:

图 2 · CPSR 高 4 位:N(**N**egative)、Z(**Z**ero)、C(**C**arry)、V(o**V**erflow)
- N(Negative,bit 31):差值为负时置 1——读作"R0 比 R1 小"
- Z(Zero,bit 30):差值为零时置 1——读作"两数相等"
- C(Carry,bit 29):减法无需借位时置 1——读作"无符号意义下 R0 ≥ R1"
- V(oVerflow,bit 28):有符号溢出时置 1
易错点: C 标志在减法语境下的规则是反直觉的——C=1 表示"没借位",C=0 才表示"借了位"。4 - 5 需要借位,所以 C=0;5 - 4 不需要借位,所以 C=1。别把它当成"有借位就置 1",正好相反。
4. 三次单步实验:标志位全写着
光说不练假把式,拿调试器做单步实验(视频里的 GDB + GEF 演示)。初始时 CPSR 全零,连续做三次 CMP:

图 3 · 三次单步实验:谁大谁小,标志位全写着
asm
mov r0, #4
mov r1, #5
cmp r0, r1 @ 4 - 5 = -1 → N=1(且借了位,C=0)
mov r0, #5
mov r1, #4
cmp r0, r1 @ 5 - 4 = 1 → 全灭,但无借位 → C=1
mov r0, #5
mov r1, #5
cmp r0, r1 @ 5 - 5 = 0 → Z=1,且无借位 → C=1三次实验,三种标志组合。调试器里看得清清楚楚:第一次 CPSR 最高位变成 8(N 置位),第二次 C 亮,第三次 Z 和 C 同时亮。CMP 从不直接告诉你"谁大",但标志位的组合就是答案:
- N=1 → R0 < R1(有符号)
- Z=1 → R0 = R1
- Z=0 且 N=0 → R0 > R1(有符号)
- C=0 → R0 < R1(无符号,有借位)
5. CMP 与 SUB:同一条减法,两种分工
把两条指令摆在一起对比,设计意图就一目了然了:

图 4 · CMP 与 SUB:同一条减法,两种分工
| 指令 | 差值去向 | 标志位 |
|---|---|---|
sub r2, r0, r1 | 写回 R2 | 默认不动(要加 S 后缀变 subs) |
cmp r0, r1 | 直接丢弃 | 必然刷新 N/Z/C/V |
关键性质:要结果用 SUB,要判断用 CMP。 两者底层走的是同一个减法器,区别只在"结果存不存、标志动不动"。
6. 为什么需要 CMP:为分支铺路
CMP 自己不做任何决定——它只是把证据(标志位)留在 CPSR 里。真正拿着证据做决定的,是下一课的主角:条件分支指令。
BEQ(相等则跳)会去看 Z 标志,BLT(小于则跳)会去看 N 和 V 的组合……它们全都指望 CMP 提前把标志布置好。所以 ARM 代码里你会反复看到这个组合拳:
asm
cmp r0, r1 @ 侦察兵:布置标志
beq somewhere @ 决策者:看标志行事小结
这一课只有一条指令,但它是整个分支体系的地基:
- CMP 语义:暗中计算 Rn - Rm,差值直接丢弃,不写回任何寄存器
- 痕迹所在:必然刷新 CPSR 的 N/Z/C/V 四个标志
- 读标志:N=1 是"小",Z=1 是"等",C=1 是无符号的"≥"
- 反直觉点:减法里 C=1 表示"没借位",C=0 才是"借了位"
- 与 SUB 的分工:要结果用 SUB,要判断用 CMP;CMP ≡ 扔掉结果的 SUBS
- 存在意义:为条件分支布置证据,cmp + 条件跳转是 ARM 最高频的组合拳
侦察兵已经就位。下一课我们就请出决策者——B 指令和它的条件后缀家族(BEQ、BNE、BLT、BGT……),让程序真正学会"如果……就……"。下一课见。