技术

ICS 与 8086 复习 08:控制转移 子程序与串操作

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 提供的接口约定,不能直接当作其他操作系统中的通用显示指令。

自测与答案

  1. cmp ax,bx 后,AX、BX 按无符号解释,要在 AX≤BX 时跳转应使用什么?按有符号解释呢?
  2. 调用前 SP=0200H,执行一次远 CALL 后,假设子程序不额外压栈,SP 是多少?栈顶首先保存的是 CS 还是返回 IP?
  3. 从八字符字符串首部向高地址扫描,第六个字符匹配,初始 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 手册。页码按文件中的幻灯片顺序计。

系列导航

CONTINUE / PATH

从这里继续

顺着当前内容继续,或者换到另一个板块。

讨论这篇文章

关于《ICS 与 8086 复习 08:控制转移 子程序与串操作》的补充、不同意见或延伸话题,都可以留在这里。

滚动到这里时自动载入留言。