计组与微机控制
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 判断程序该往哪走。
