分支与循环
复习
- 汇编器:通过两遍扫描,把符号汇编成机器码
- 高级语言:再加一层:从“控制机器”转向“描述问题”
- 变量、类型与表达式:名字如何代表数据,表达式如何变成运算
TL;DR
- 判断落到机器上,是“比较 + 条件跳转”
- 比较的结果记在状态标志里,跳转指令根据标志决定改不改 PC
- 循环就是“往回跳”,再加上一个“该不该停下”的判断
- 顺序、选择、循环三种结构,构成了所有程序
正文
到现在为止,我们的程序只会一条道走到黑。可现实里更像这样:如果分数及格就显示“通过”;把 1 到 100 加起来;只要还没做完就再来一遍。要表达这些,程序得学会 拐弯 和 回头 。
判断:比较加跳转
先看最简单的一句:
if x > y:
max = x
else:
max = y
人一眼就懂,机器却只会计算和跳转。它怎么知道 x 大于 y?
办法还是计算:把两个数 相减 。如果 x - y 是正数,说明 x 更大;是 0,说明相等;是负数,说明 x 更小。这一步相减,就叫 比较 。
我们把比较写成一条指令 CMP:它做减法、设置状态标志,但 不保存减法结果 ——我们关心的不是差是多少,而是差的正负。然后,再由跳转指令去看标志:
LOAD A, [x]
LOAD B, [y]
CMP A, B
JGT then ; 若 x > y,跳到 then
MOV R, B ; 否则 max = y
STORE [max], R
JMP end
then: MOV R, A ; max = x
STORE [max], R
end: HALT
MOV 是“把一个寄存器里的值搬到另一个寄存器”。这里的 JGT 是“大于则跳转”。除了它,还有一批类似的指令:JZ(等于 0 则跳)、JN(为负则跳)、JLE(小于等于则跳)……名字不用记, 它们全都是先看标志,再决定改不改 PC。
顺带一提,前面几章那个 Z 标志,就是在这类地方派上用场的:ALU 负责产生标志,跳转指令负责根据标志选路。
循环:往回跳
再看“把 1 到 n 加起来”:
sum = 0
i = 1
while i <= n:
sum = sum + i
i = i + 1
翻译成汇编:
; 这里假设 sum、i、n、one 都已就位
loop: LOAD A, [i]
LOAD B, [n]
CMP A, B
JGT done ; i > n 时结束
LOAD A, [sum]
LOAD B, [i]
ADD R, A, B
STORE [sum], R ; sum = sum + i
LOAD A, [i]
LOAD B, [one]
ADD R, A, B
STORE [i], R ; i = i + 1
JMP loop ; 回到开头
done: HALT
对照着看:while 的判断变成“比较 + 不满足就跳出”;循环体照抄;最后一条 JMP loop 把 PC 改回开头,于是这几行被反复执行。
循环,本质上就是“往回跳”。 只不过每次跳之前,都要问一句“还该继续吗”。
三种结构
无论程序多复杂,拆开来都逃不出三种结构:
- 顺序 :一条接一条往下走
- 选择 :满足条件走这边,否则走那边(
if、else) - 循环 :满足条件就一直重复(
while、for)
它们全都靠“比较 + 条件跳转”实现。高级语言把这层跳转包装成了 if、while,我们就再也不用亲手写 JMP 了——这正是上一章说的“加一层”。
不过,上面这些程序还有一个共同点:它们都是从头写到尾的一长条。如果某段逻辑要反复使用,难道每次都要重抄一遍?下一章,我们给它起个名字,让它能按需调用。
思考题
如果把循环里的
i = i + 1忘了写,会发生什么?从 PC 和标志的角度,解释一下为什么程序会“停不下来”。
小结
知识点
- 判断由“比较 + 条件跳转”实现
- 比较设置状态标志,条件跳转根据标志决定是否改 PC
- 循环是“往回跳”加一个结束判断
- 顺序、选择、循环三种基本结构
参考资料
- Wikipedia(zh):控制流:程序中的顺序、选择与循环
- Wikipedia(zh):条件跳转:分支与跳转
思考题答案(仅供参考)
忘了 i = i + 1,i 永远不变,于是“i > n”这个条件永远不成立,JGT done 一直不跳,JMP loop 每次都把 PC 带回开头,程序就一直在循环里打转。它不是“卡住”,而是忠实地执行了程序——只是程序本身没有让它停下的条件。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪