← 返回 计组与微机控制

计组与微机控制

7.8 串操作和条件转移

7.8 串操作和条件转移

例 4.18:复制 200 个字节

书上例子是把 BUFFER1 开头的 200 个字节传到 BUFFER2。

核心代码是:

LEA SI, BUFFER1 LEA DI, BUFFER2 MOV CX, 200 CLD REP MOVSB

逐句看:LEA SI, BUFFER1把源串首地址放入 SI。

LEA DI, BUFFER2 把目的串首地址放入 DI。

MOV CX, 200 指定要传送 200 个字节。

CLD让 SI、DI 每次自动递增。

REP MOVSB 执行 200 次:

ES:[DI] ← DS:[SI] SI + 1 DI + 1 CX - 1

直到 CX = 0。

所以它相当于高级语言里的:

for (i = 0; i < 200; i++) BUFFER2[i] = BUFFER1[i];

一、LODS:串装入指令

LODS 是从源串取数据到累加器。

常见形式:

LODSB LODSW

LODSB:AL ← DS:[SI]

然后根据 DF 修改 SI。

LODSW:AX ← DS:[SI]

然后根据 DF 修改 SI。注意,LODS 只有源地址:DS:SI

它没有目的串,因为目的默认就是 AL 或 AX。

二、STOS:串送存指令

STOS 是把 AL 或 AX 送到目的串。

常见形式:

STOSB STOSW

STOSB:ES:[DI] ← AL

STOSW:ES:[DI] ← AX

然后根据 DF 修改 DI。

它没有源串,因为源默认就是 AL 或 AX。

例 4.19:小写字母转大写

这个例子很经典。任务是:把 BLOCK1 中的数据复制到 BLOCK2,同时把小写字母 a~z 转成大写 A~Z,遇到回车符 CR 结束。

关键知识是 ASCII 码:

'a' = 61H 'z' = 7AH 'A' = 41H 'Z' = 5AH

小写转大写,只需要:小写 ASCII - 20H = 大写 ASCII

例如:61H - 20H = 41H 'a' = 'A'

程序逻辑

整体过程是:从 BLOCK1 取一个字符,判断是不是回车,如果是,结束 判断是不是小写字母,如果不是,原样送到 BLOCK2 如果是,减 20H 转成大写,送到 BLOCK2 继续处理下一个字符

核心代码逻辑可以看成:

LODSB CMP AL, 0DH JZ DONE CMP AL, 61H JC OK CMP AL, 7BH JNC OK SUB AL, 20H OK: STOSB JMP NEXT

为什么比较 7BH,不是 7AH?

因为小写字母最大是:'z' = 7AH

程序用:

CMP AL, 7BH JNC OK

意思是:如果 AL >= 7BH,就不是小写字母

所以真正会被转换的是:61H ≤ AL ≤ 7AH也就是 a~z。

JC 和 JNC 怎么理解?

CMP AL, 61H JC OK

CMP 本质是:AL - 61H

如果 AL < 61H,无符号减法需要借位,所以 CF = 1,于是 JC 跳转。

所以这句意思是:如果 AL < 'a',不是小写字母,直接保存

CMP AL, 7BH JNC OK

如果 AL >= 7BH,不需要借位,CF = 0,于是 JNC 跳转。

这句意思是:如果 AL > 'z',不是小写字母,直接保存

LODSB 和 STOSB 的配合

LODSB

做两件事:AL ← DS:[SI] ,SI 自动 +1

STOSB

也做两件事:ES:[DI] ← AL ,DI 自动 +1

所以程序员不用手写:INC SI,INC DI

这就是串操作指令的方便之处。

三、CMPS:串比较指令

CMPS 用于比较两个字符串或者两段内存。

常见形式:

CMPSB CMPSW

CMPSB 的本质是:DS:[SI] - ES:[DI]

它不保存结果,只影响标志位。

然后 SI 和 DI 自动修改。

CMPS 常和 REPE 配合

如果要比较两个字符串是否相同,一般写:

CLD MOV CX, 长度 REPE CMPSB

含义是:只要当前字符相等,并且 CX 还没减到 0,就继续比较

一旦遇到第一个不同字符,ZF = 0,REPE 停止。

如果全部相等,最后 CX = 0。

四、例 4.20:找两个字符串第一个不同字符的位置

书上例子是比较 STRING1 和 STRING2,长度都是 20 字节。

核心代码:

LEA SI, STRING1 LEA DI, STRING2 MOV CX, 20 CLD REPE CMPSB JCXZ ALLMATCH DEC SI DEC DI

逐句看

REPE CMPSB 它会重复比较:DS:[SI] 和 ES:[DI]

如果相等,继续。如果不相等,停止。

为什么后面要 DEC SI、DEC DI?

因为 CMPSB 每比较一次后,不管相等不相等,都会自动让 SI 和 DI 加 1。

所以当发现第一个不同字符时,SI 和 DI 已经指向下一个字符了。

因此要退回来:

DEC SI DEC DI

这样 SI 和 DI 才重新指向那个“不相等字符”的位置。

JCXZ ALLMATCH 是什么意思?

JCXZ ALLMATCH

意思是:如果 CX = 0,就跳转

如果 REPE CMPSB 是因为 CX 减到 0 才停止,说明 20 个字符全都比较完了,而且都相等。

所以跳到 ALLMATCH。

如果不是 CX = 0,而是中途遇到不同字符停下,那就说明找到了第一个不同字符。

五、SCAS:串扫描指令

SCAS 用于在字符串中搜索某个值。

常见形式:

SCASB SCASW

SCASB 的本质是:AL - ES:[DI]

它拿 AL 中的关键字和 ES:DI 指向的字符串元素比较。

如果相等:ZF = 1如果不等:ZF = 0然后 DI 自动修改。

SCAS 常和 REPNE 配合

比如查找某个字符:

MOV AL, KEY MOV CX, 长度 CLD REPNE SCASB

含义:只要没找到,并且 CX 还不为 0,就继续扫描

找到时:ZF = 1没找到并扫描完时:CX = 0

例 4.21:搜索关键字

任务:在 STRING 中找 KEY_WORD。

如果找到:显示 'Y' 保存关键字地址 保存搜索次数

如果没找到:显示 'N'

核心代码逻辑:

LEA DI, STRING MOV AL, KEY_WORD MOV CX, 100 CLD REPNE SCASB JZ MATCH

为什么找到后要 DEC DI?

SCASB 每扫描一次后,DI 会自动加 1。

所以如果找到了,DI 已经指向关键字的下一个位置了。

因此:DEC DI把 DI 调回关键字所在地址。

COUNT 为什么要用 DI - STRING + 1?

找到后,程序大概做的是:

LEA BX, STRING SUB DI, BX INC DI MOV COUNT, DI

假设关键字在第一个位置:DI - BX = 0

但搜索次数应该是 1,所以要:+1

因此:COUNT = 找到位置相对于首地址的偏移 + 1

也就是第几个元素被找到。

串操作指令小结

这一节的共同点:

源串一般是 DS:SI 目的串一般是 ES:DI CX 控制重复次数 DF 控制地址增加还是减少

常见搭配:

CLD REP MOVSB

用于复制内存。

CLD REP STOSB

用于填充内存。

CLD REPE CMPSB

用于比较字符串是否相等。

CLD REPNE SCASB

用于查找某个字符。

六、控制转移类指令

后面开始讲控制转移类指令。所谓控制转移,就是改变程序的执行顺序。

正常情况下,CPU 是:执行当前指令,IP 自动指向下一条指令,继续执行

而控制转移类指令会修改:IP

或者同时修改:CS 和 IP

于是程序跳到别的地方执行。

控制转移分几类?

书上说主要包括:无条件转移 JMP,条件转移 Jcc 循环控制 LOOP,过程调用 CALL 中断 INT,这几类共同点是:改变 IP,或者同时改变 CS:IP

一、NEAR 和 FAR

1. NEAR:段内转移

如果目标地址还在当前代码段 CS 内,只需要改 IP。

这种叫:NEAR 段内转移

也就是:CS 不变,IP 改变

2. FAR:段间转移

如果目标地址在另一个代码段,就必须同时修改 CS 和 IP。

这种叫:FAR段间转移

也就是:CS 改变IP 改变

直接转移和间接转移

还有一组概念:直接转移,间接转移

1. 直接转移

目标地址直接写在指令里。

例如:JMP LABEL

或者:JMP FAR PTR 2000H:1000H

目标很明确,直接出现在指令中。

2. 间接转移

目标地址不直接写死,而是放在寄存器或内存中。

例如:JMP BX

意思是:IP ← BX

或者:JMP DWORD PTR [SI]

意思是从内存中取出 4 字节:低 2 字节 → IP,高 2 字节 → CS

也就是段间间接转移。

二、JMP 无条件转移

JMP 一共有五种主要形式。

1. 段内直接短转移

JMP SHORT label

特点:只修改 IP,位移是 8 位 范围是 -128 ~ +127适合跳得比较近。

2. 段内直接近转移

JMP NEAR PTR label

特点:只修改 IP,位移是 16 位,跳转范围更大

3. 段内间接转移

JMP BX JMP WORD PTR [SI]

特点:只修改 IP目标偏移地址来自寄存器或内存

例如:JMP BX

就是:IP ← BX

4. 段间直接转移

JMP FAR PTR label

特点:同时修改 CS 和 IP 目标地址直接写在指令中

例如书上的:JMP FAR PTR LAB1

表示跳到另一个代码段中的 LAB1。

5. 段间间接转移

JMP DWORD PTR [SI]

特点:同时修改 CS 和 IP 目标地址存放在内存中的连续 4 个字节里

注意顺序:

[SI] 和 [SI+1] → IP [SI+2] 和 [SI+3] → CS

因为 8086 是小端存储,所以低地址放低字节。

三、条件转移指令 Jcc

条件转移的基本格式:Jcc label

比如:

JZ NEXT JC ERROR JS NEGATIVE

它根据标志寄存器中的某些标志位决定是否跳转。

1. 条件转移只支持短跳转

8086 的条件转移位移是 8 位。

所以跳转范围是:-128 ~ +127 字节也就是说,目标不能太远。

2. 条件转移不影响标志位

Jcc 只是读取标志位,不修改标志位。

所以一般写法是:

CMP AX, BX JA ABOVE

其中:CMP AX, BX负责设置标志位。

JA ABOVE负责根据标志位跳转。

四、常见条件转移分类

条件转移可以分成几组。

1. 看单个标志位

比如:JZ / JE条件:ZF = 1

表示结果为 0,或者比较结果相等。

JNZ / JNE

条件:ZF = 0

表示结果不为 0,或者比较结果不相等。

JC条件:CF = 1

表示有进位或有借位。

JNC条件:CF = 0

表示无进位或无借位。

JS条件:SF = 1

表示结果符号位为 1,即负数。

JNS条件:SF = 0表示结果非负。

JO条件:OF = 1表示有溢出。

JNO条件:OF = 0表示没有溢出。

2. 无符号数比较

无符号比较看:CF 和 ZF

常用:JA JAE JB JBE

含义:

JA above, 大于 JAE above/equal,大于等于 JB below, 小于 JBE below/equal,小于等于

例如:

CMP AL, BL JA BIGGER

意思是按无符号数比较:如果 AL > BL,则跳转

对应条件:JA:CF = 0 且 ZF = 0因为无借位且不相等,就说明大于。

3. 带符号数比较

带符号比较看:

SF、OF、ZF

常用:JG JGE JL JLE

含义:

JG greater, 大于 JGE greater/equal, 大于等于 JL less, 小于 JLE less/equal, 小于等于

例如:

CMP AL, BL JG BIGGER

意思是按带符号数比较:如果 AL > BL,则跳转

带符号比较不能只看 CF,因为 CF 是无符号借位。

4. JCXZ

JCXZ label

条件是:CX = 0

这个指令不看标志位,而是直接看 CX。

它常用于循环或者串操作后判断是否全部处理完。

无符号比较和带符号比较的区别

这个非常容易考。

比如:AL = FFH,BL = 01H

如果按无符号数看:FFH = 255,01H = 1

所以:AL > BL

应该用:JA

如果按带符号数看:FFH = -1,01H = 1

所以:AL < BL

应该用:JL

因此:

无符号数:JA/JAE/JB/JBE 带符号数:JG/JGE/JL/JLE

不要混用。

五、OR AL, AL 的作用

OR AL, AL这句非常常见。

它的效果是:AL 的值不变 但是根据 AL 的内容设置标志位

因为任何数 OR 自己还是自己。

它主要用来判断 AL 是:正数,负数,零

执行:OR AL, AL

之后:

如果 AL = 0,则 ZF = 1 如果 AL 最高位为 1,则 SF = 1 如果 AL 最高位为 0 且 AL ≠ 0,则是正数

所以后面可以接:

JS X1 ;负数 JZ X2 ;零

如果两个都不跳,那就是正数。

六、例 4.23:统计正数、负数和零的个数

任务:数据段中有若干个 8 位带符号数,统计:

正数个数 → PLUS 负数个数 → MINUS 零的个数 → ZERO

程序开头:

XOR AL, AL MOV PLUS, AL MOV MINUS, AL MOV ZERO, AL

这里:XOR AL, AL把 AL 清 0。

然后把三个计数单元都清 0。

接下来:

LEA SI, TABLE MOV CX, COUNT CLD

含义:SI 指向数据表首地址 CX 是数据个数 DF = 0,后面 LODSB 后 SI 自动递增

循环主体:

CHECK: LODSB OR AL, AL JS X1 JZ X2 INC PLUS JMP NEXT

逐句看:LODSB取一个带符号字节到 AL。

OR AL, AL不改变 AL,只设置标志位。

JS X1如果 SF = 1,说明 AL 是负数,跳到 X1。

JZ X2如果 ZF = 1,说明 AL 是 0,跳到 X2。

如果既不是负数,也不是零,那就是正数:INC PLUS正数个数加 1。

负数分支:

X1: INC MINUS JMP NEXT

零分支:

X2: INC ZERO

后面应该会接类似:

NEXT: LOOP CHECK

因为要继续处理下一个数据,直到 CX 减到 0。

总结、这一段最重要的记忆点

串操作:

MOVS:DS:SI → ES:DI LODS:DS:SI → AL/AX STOS:AL/AX → ES:DI CMPS:DS:SI 与 ES:DI 比较 SCAS:AL/AX 与 ES:DI 比较

重复前缀:

REP:CX ≠ 0 就重复 REPE/REPZ:CX ≠ 0 且 ZF = 1 就重复 REPNE/REPNZ:CX ≠ 0 且 ZF = 0 就重复

控制转移:

NEAR:只改 IP FAR:改 CS 和 IP 直接转移:目标地址在指令里 间接转移:目标地址在寄存器或内存里

条件转移:

无符号比较:JA/JAE/JB/JBE,看 CF/ZF 带符号比较:JG/JGE/JL/JLE,看 SF/OF/ZF

这一页最核心的思想是:串操作靠 SI、DI、CX、DF 自动处理连续内存;条件转移靠 FLAGS 判断程序该往哪走。