Skip to content

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

CMP 比较指令封面

上一课的移位与旋转为位操作家族收了官。从这一课开始,我们向真正的"复杂指令"进发——目标是让 CPU 学会做选择(分支)。但在做选择之前,得先解决一个前置问题:CPU 怎么知道两个数谁大谁小、是否相等?

答案就是今天的主角 CMP。它是整个分支体系的侦察兵:所有"如果……就……"的判断,都要先靠它探明敌情。

1. 比较的本质:减一下就知道

想知道 R0 和 R1 谁大?小学数学就够了:做个减法,看结果的正负零。

  • 差是负数 → R0 比 R1 小
  • 差是正数 → R0 比 R1 大
  • 差是零 → 两个数相等

CMP 干的就是这件事。cmp r0, r1 会在 CPU 内部暗中计算 R0 - R1,然后根据差值刷新 CPSR 的标志位。比较的全部信息,就藏在这几个标志里。

图 1 · CMP 的本质:暗中计算 R0 - R1,差值直接丢弃不写回寄存器,但必然刷新 CPSR 的 N/Z/C/V 标志

图 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 在差值为负时置 1,Z 在差值为零时置 1,C 在减法无需借位时置 1,V 在有符号溢出时置 1

图 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 · 三次 CMP 实验对照:cmp 4,5 点亮 N;cmp 5,4 点亮 C;cmp 5,5 点亮 Z 和 C

图 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 把差值写回寄存器、默认不动标志;CMP 丢弃差值、必然刷新标志

图 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……),让程序真正学会"如果……就……"。下一课见。

本章自测