ICS 与 8086 复习 08:控制转移 子程序与串操作
一段程序何时跳转、调用后如何回来、字符串比较何时停止,都依赖 CPU 中已经存在的状态。读这类代码,不能只把指令翻译成中文,还要持续跟踪 IP、SP、CX、标志位以及隐含的串指针。
本文以 8086 实模式为范围,把控制流、近远调用和串操作放在一起复习。它们的共同考法是给出初态,要求推导下一位置、栈内容或循环结束状态。
条件跳转先确定比较类型
cmp ax,bx 按 AX−BX 设置标志,但不会改变 AX、BX。接下来选什么跳转,要看题目把两个数解释为无符号数还是有符号数。
| 关系 | 无符号跳转及条件 | 有符号跳转及条件 |
|---|---|---|
| 小于 | JB,CF=1 |
JL,SF≠OF |
| 大于等于 | JAE,CF=0 |
JGE,SF=OF |
| 大于 | JA,CF=0 且 ZF=0 |
JG,ZF=0 且 SF=OF |
| 小于等于 | JBE,CF=1 或 ZF=1 |
JLE,ZF=1 或 SF≠OF |
相等与不等都检查 ZF,因此 JE/JZ 和 JNE/JNZ 分别是同义写法。
例如 AL=80H、BL=01H,执行 cmp al,bl,内部差值为 7FH,CF=0、SF=0、OF=1。无符号解释是 128 大于 1,所以 JA 成立;有符号解释是 −128 小于 1,所以 JL 也成立。这并不矛盾,而是在回答不同的问题。
不能仅用 JS 判断一般的有符号“小于”。发生有符号溢出时,SF 必须结合 OF 才能正确解释比较结果。CMP 与 Jcc 之间也不要随意插入会覆盖相关标志的算术或逻辑指令。
相对转移从下一条指令计算
8086 的短转移使用八位有符号位移,范围为 −128 至 +127。相对位移的基准是转移指令结束后的 IP,不是该指令的首地址。
若某条短转移的下一条指令在 0102H,编码中的位移字节为 FCH,它作为八位补码表示 −4,因此目标为 0102H−4=00FEH。
8086 的普通条件跳转只提供短转移形式。目标太远时,可用相反条件跳过一条较远的 JMP;不能把后来 x86 的近条件跳转形式默认为 8086 本机指令。
段内转移只改变 IP;远转移同时改变 CS、IP。内存中的 16:16 远指针先存偏移字,再存段值字,每个字内部仍按小端顺序存放。
LOOP 的零次陷阱
LOOP 先将 CX 减 1,若结果非零就跳转。它不改变条件标志;LOOPE、LOOPNE 还会分别检查已有的 ZF 是否为 1 或 0。
所以 loop again 与 dec cx、jnz again 可以产生类似的计数转移,却不能认为执行效果完全一致:DEC 会修改部分条件标志,而 LOOP 保留它们。
下面的求和框架先处理空数组:
mov cx, count
mov ax, 0
mov si, 0
jcxz done
again:
add ax, array[si]
add si, 2
loop again
done:
mov total, ax
若省略 JCXZ,且初始 CX=0,程序仍先执行一次循环体;LOOP 再把 0 减成 FFFFH,整个框架会执行 65536 次循环体,远超“零个元素”的含义。
本例中 add si,2 会覆盖求和指令产生的标志。因此,退出后若想依据标志判断加法是否溢出,应在每次 ADD 后立即检查并记录,不能把最后一次循环留下的标志当作累计和的状态。
CALL 与 RET 按近远配对
JMP 只转移,CALL 还要保存返回地址。对 8086:
- 近 CALL 将下一条指令的 IP 压栈,占两个字节。
- 远 CALL 先压返回 CS,再压返回 IP,共占四个字节。
- 近 RET 弹出 IP;远 RET 依次弹出 IP、CS。
设调用前 CS=2000H、SP=0100H,返回 IP 已知为 0123H,且被调用程序没有额外压栈或已恢复现场。
| 调用形式 | 进入被调用程序时 SP | 从栈顶向高地址的返回信息 |
|---|---|---|
| 近调用 | 00FEH |
0123H |
| 远调用 | 00FCH |
0123H、2000H |
相应返回后 SP 恢复为 0100H。若把远调用错误配成近返回,CS 没有恢复,栈里还残留一个字,后续控制流就会出错。
ret 6 的含义是先恢复返回地址,再额外将 SP 加 6,以丢弃六个字节的参数;其中的 6 不是返回地址,也不表示“弹出六个字”。在 MASM 中,PROC 的 NEAR/FAR 属性还会影响 RET 的汇编形式,读题时必须一起查看。
保存寄存器同样要遵守后进先出。若子程序先 PUSH AX、再 PUSH BX,返回前应先 POP BX、再 POP AX;否则即使 SP 最终恢复,寄存器值也可能互换。
串指令依赖隐含操作数
串指令可以按字节或字处理连续数据,B 后缀表示一次处理一个字节,W 后缀表示一个字。它们使用的地址和数据如下。
| 指令 | 主要操作 |
|---|---|
| MOVSB/MOVSW | DS:SI → ES:DI |
| STOSB/STOSW | AL/AX → ES:DI |
| LODSB/LODSW | DS:SI → AL/AX |
| CMPSB/CMPSW | 比较 DS:SI − ES:DI |
| SCASB/SCASW | 比较 AL/AX − ES:DI |
CLD 令 DF=0,相关指针每次增加 1 或 2;STD 令 DF=1,相关指针每次减少 1 或 2。LODS 的目的在 AL/AX,没有 ES 目的串。
REP 配合 MOVS、STOS 时,CX 记录元素个数,因此 rep movsw 的 CX=10 表示复制十个字,即二十字节。执行前要正确初始化段寄存器、指针、计数和 DF,单独写 LEA 并不能装载 DS 或 ES。
REPE/REPZ 与 REPNE/REPNZ 常配合 CMPS、SCAS。CX 非零时先进行一次比较,再根据该次比较得到的 ZF 决定是否继续;不需要预先设置 ZF。初始 CX=0 时不执行比较,ZF 保持旧值,因此不能用它断言查找结果。Intel 指令手册 REP
查找题要处理已经移动的 DI
假设 ES 已正确指向字符串所在段,mess DB 'COMPUTER',执行:
lea di, mess
mov al, 'T'
mov cx, 8
cld
repne scasb
比较依次经过 C、O、M、P、U、T,共六次。第六次相等,ZF=1,停止重复;CX=8−6=2,DI 已指向 T 后面的 E,即 OFFSET mess+6。匹配字符在 DI−1。
若最后一个字符才匹配,CX 也会变成零,所以不能只根据 CX=0 判断“没找到”;还要检查最后比较产生的 ZF。反向扫描时指针递减,匹配位置则要按相反方向修正。
在缓冲区重叠的复制题中,方向还决定尚未复制的数据会不会提前被覆盖。若目标位于源区内部的较高地址,通常要从末尾向前复制,并把 SI、DI 先定位到各自末尾;只写 STD 却仍从首地址开始,会访问错误的范围。
中断返回需要恢复标志
8086 的 INT n 通过中断向量进入处理程序。中断向量表从物理地址 0 开始,每项四字节,类型 n 对应 4×n 处的偏移字及随后段值字。
执行软件 INT 时,CPU 保存 FLAGS、CS 和返回 IP,并清除 IF、TF;处理程序通常用 IRET 依次恢复 IP、CS、FLAGS。因此,IRET 不能用普通的近 RET 替代。Intel 指令手册 INT 与 IRET
课件中的 int 21h 使用 DOS 约定,AH 选择具体服务。例如 AH=09H 显示 DS:DX 指向、以 $ 结束的字符串。这是 DOS 提供的接口约定,不能直接当作其他操作系统中的通用显示指令。
自测与答案
cmp ax,bx后,AX、BX 按无符号解释,要在 AX≤BX 时跳转应使用什么?按有符号解释呢?- 调用前 SP=
0200H,执行一次远 CALL 后,假设子程序不额外压栈,SP 是多少?栈顶首先保存的是 CS 还是返回 IP? - 从八字符字符串首部向高地址扫描,第六个字符匹配,初始 CX=8,REPNZ SCASB 结束后 CX 与 DI 如何变化?
答案:1. 无符号用 JBE,有符号用 JLE。2. SP=01FCH,栈顶是返回 IP,其后是返回 CS。3. CX=2,DI 比初值增加 6,匹配字符在 DI−1。
对应课件
《汇编语言 第3章.pptx》:第 34—43 页转移寻址,第 101—120 页串操作,第 121—132 页控制转移与循环,第 133—138 页子程序调用返回;第 121 页列出 INT、INTO、IRET,中断进入与返回的简述补充参照上述 Intel 手册。页码按文件中的幻灯片顺序计。
讨论这篇文章
关于《ICS 与 8086 复习 08:控制转移 子程序与串操作》的补充、不同意见或延伸话题,都可以留在这里。
滚动到这里时自动载入留言。