第3章 80x86汇编语言程序设计(下)
3.5 分支结构程序设计 分支结构是指计算机根据实际情况或条件,作出判断和选择,转而执行不同的程序段的一种程序结构。 Y N N 条件 条件 程序段A 程序段B
根据某个控制字的各“位”状态实行多路转移 多路分支结构 根据某个控制字的各“位”状态实行多路转移 多路条件测试 程序段1 程序段2 …… 程序段n
3.5.1无条件转移指令 JMP 1、段内转移 格式1:JMP SHORT OPR ;段内相对短转移 操作1:IP<--(IP)+disp8 说明:转移范围-128字节至+127字节,操作数OPR为段内某个标号。 段内相对短转移示例 指令JMP SHORT ADDT 存放在CS:0200H 中, 标号ADDT对于IP指针的偏移量为1DH, 则转移地址为0202H+001DH=021FH JMP SHORT ADDT 0200 E8 … 1 1D ADDT: MOV AL, 40H 2 ADD AL, BL +1DH 021F B0 0220 1D
格式2:JMP OPR JMP NEAR PTR OPR ;段内相对近转移 操作2:IP<--(IP)+disp16 说明:转移范围-32KB至+32KB, 操作数OPR为段内某个标号。 格式3:JMP WORD PTR OPR ;段内间接转移 操作3:IP<--(OPR) 说明:OPR是基址/变址寄存器或存储器操作数。 段内间接转移示例 ADDRESS DW 2000H ;定义转移地址 ... LEA SI, ADDRESS ;偏移量-->SI JMP WORD PTR[SI] ;转移到CS:2000
格式4:JMP FAR PTR OPR ;段间直接转移 操作4:IP<--OAopr CS<--(CS)opr 2、段间转移 格式4:JMP FAR PTR OPR ;段间直接转移 操作4:IP<--OAopr CS<--(CS)opr C1段 EA OP码 段间直接转移示例 50 新IP=0250H ;代码段C1 02 …… 00 新CS=2000H JMP FAR PTR NEXT 20 …… ;代码段C2 C2段 20000H NEXT: MOV AL,10H … NEXT 20250H
格式5:JMP DWORD PTR OPR ;段间间接转移 操作5:IP<--((DS)*16+OPR) CS<--((DS)*16+OPR+2) 段间间接转移示例 JMP DWORD PTR [4000H] 设 (DS)=1000H (14000H)=0010H (14002H)=5000H 执行后 (CS)=5000H (IP)=0010H
3.5.2 条件转移指令 格式:J条件 标号 操作:测试条件,若满足,则跳转到标号处执行, 即 IP<--(IP)+disp8 ; 否则,执行后续指令 说明:根据上一条指令所设置的条件码判别测试条件 转移范围在-128到+127字节
条件转移指令(1) 操作符 功能 测试条件 JC 进位标志为1转移 CF=1 JNC 进位标志为0转移 CF=0 操作符 功能 测试条件 JC 进位标志为1转移 CF=1 JNC 进位标志为0转移 CF=0 JZ/JE 等于0/相等转移 ZF=1 JNZ/JNE 不等于0/不相等转移 ZF=0 JS 符号标志为1转移 SF=1 JNS 符号标志为0转移 SF=0 JO 溢出转移 OF=1 JNO 无溢出转移 OF=0 JP/JPE 偶状态转移 PF=1 JNP/JPO 奇状态转移 PF=0 JCXZ CX=0转移 CX=0 JECXZ ECX=0转移 ECX=0 JPE E=EVEN JPO O=ODD
条件转移指令(2) A-B 比较情况 无符号数 有符号数 指令 判断条件 指令 判断条件 A>B JA ZF=0,CF=0 JG SF=OF JNBE JNLE 且 ZF=0 A>=B JAE ZF=1或 JGE SF=OF JNB CF=0 JNL 或ZF=1 A<B JB ZF=0,CF=1 JL SF<>OF JNAE JNGE 且ZF=0 A<=B JBE ZF=1或 JLE SF<>OF JNA CF=1 JNG 或ZF=1
条件转移指令示例1 将X中十六进制的ASCII码转换成其所对应的数值,存放到HEX中。如‘A’应转换为10。 注意ASCII中‘0’~‘9’是30H~39H,’A’~’F’是41H~46H MOV AH, X CMP AH, 39H JBE NEXT ;≤39H则转 SUB AH, 7 ;是‘A’--’F’,减7 NEXT: SUB AH, 30H ;减30H MOV HEX, AH
如果条件转移目标地址超出-128~+127的范围怎么办? 条件转移指令示例2 CMP AX, 0FFFFH JLE P3 CMP AX,1 JL P2 MOV AX, 1 JMP DONE P2: MOV AX, 0 P3: MOV AX,-1 DONE: AX≤(-1) Y N N Y AX<1 AX<--1 AX<--0 AX<--(-1) 如果条件转移目标地址超出-128~+127的范围怎么办?
3.5.3 分支结构程序设计 教材上的例子请看P99中的例3.18和例3.19,上机实现例3.18程序。 1、比较/转移 利用比较和条件转移指令实现两路分支。 比较结果记录在某些标志位中,条件转移指令 根据约定的条件进行对照,满足条件时转移,不满 足条件时不转移。 2、跳转表转移 利用跳转表实现多路分支。 比较/转移指令可嵌套,但程序结构复杂, 跳转表可使程序结构清晰。 教材上的例子请看P99中的例3.18和例3.19,上机实现例3.18程序。
有一个首地址为ARRAY的N字数组,将其中正数的个数放在DI中,0的个数放在SI中,负数个数放在AX中
MOV CX, N MOV BX, 0 ;初始化 MOV DI, BX ; 正数个数计数器初始化 MOV SI, BX ; 0的个数计数器初始化 AGAIN:CMP WORRD PTR ARRAY[BX], 0 ;数组当前元素与0比较 JLE LEEQ ;小于等于0转移 INC DI ;正数计数 JMP NEXT LEEQ: JL NEXT ;小于0转移 INC SI ;0计数 NEXT: ADD BX, 2 ;数组表指针指向下一元素 DEC CX JNZ AGAIN MOV AX, N ;负数个数=N-DI-SI SUB AX, DI SUB AX, SI
AL<--(N1),AH<--(N2) D1<--(AL),D2<--(AH) 设字节单元N1、N2中存放无符号数 (1)若两个均是偶数,则分别加1后送D1 、D2中 (2)若两个均是奇数,则直接送D1 、D2中 (3)若一个是奇数,一个是偶数,则把奇数送D1,偶数送D2中 AL<--(N1),AH<--(N2) 注意:根据条件,当N1是奇数时,无论N2是奇数还是偶数,都只需直接送D1、D2 偶 (AL)0=0 奇 偶 奇 (AH)0=0 (AL) (AH) AL<--(AL)+1 AH<--(AH)+1 D1<--(AL),D2<--(AH)
程序如下: MOV AL, N1 MOV AH, N2 TEST AL, 01H ;测试 N1的奇偶 JNE ENDO ;N1为奇数 TEST AH, 01H ;测试 N2的奇偶 JNE L1 ;N2是奇数,转移 INC AL ;两个均是偶数 INC AH JMP ENDO L1: XCHG AL, AH ;N1是偶数, N2是奇数 ENDO: MOV D1, AL ;存放结果 MOV D2, AH 转上页
利用跳转表实现多路分支 跳转表是在某一内存区域顺序排列的一组有规律的入口地址。 如是段内分支,每个地址占两个单元(IP的值) 如是段间分支,每个地址占4个单元(CS:IP的值) TABLE SUB1 TABLE SUB1 SUB2 SUB3 SUB2 IP IP IP CS IP IP CS 段内转移 段间转移
根据AL中哪一位为1(从低位到高位)把程序转移到8个不同的程序分支去 .DATA TABLE DW ROUTINE_1 DW ROUTINE_2 DW ROUTINE_3 DW ROUTINE_4 DW ROUTINE_5 DW ROUTINE_6 DW ROUTINE_7 DW ROUTINE_8 .CODE …… ROUTINE_1: MOV AX,0 ROUTINE_2: CMP AL, 3 ROUTINE_8: CLI
用寄存器间接寻址与基址变址寻址该如何做? 用变址寻址方式 CMP AL, 0 JE DONE MOV SI, 0 L: SHR AL, 1 JNB NOT_YET ; CF=0或ZF=1跳转 JMP TABLE[SI] NOT_YET: JZ DONE ADD SI, TYPE TABLE ;Type Table=2 JMP L DONE: …… 方法2 用寄存器间接寻址方式 CMP AL, 0 JE DONE LEA BX, TABLE L: SHR AL, 1 JNB NOT_YET JMP WORD PTR[BX] NOT_YET: JZ DONE ADD BX, TYPE TABLE JMP L DONE: …… 方法3 用基址变址寻址方式 MOV SI, 7*TYPE TABLE MOV CX, 8 L: SHL AL, 1 JMP WORD PTR[BX][SI] SUB SI, TYPE TABLE 用寄存器间接寻址与基址变址寻址该如何做?
在附加段中有一个从小到大排序的无符号数字数组,其首地址在DI中,数组的第一个单元存放数组长度。要求在数组中查找(AX),如找到,CF=0,并在SI中给出该元素在数组中的偏移地址;如未找到,CF=1。 算法:在R数组中查找K,采用折半查找法 LOW1, HIGHN; 若LOW>HIGH,则查找失败,置CF=1,退出程序。否则,计算中点:MID(LOW+HIGH)/2; (3) K与R[MID]比较。若=R[MID],则查找成功,程序结束; 若K<R[MID]则转(4);若K>R[MID],则转(5); (4) HIGHMID-1,转(2); (5) LOWMID+1,转(2)。
CMP AX, ES:[DI+2] ;与第一个数比较 JA CHK_LAST ;(AX)>(ES:[DI+2]) 转 LEA SI, ES:[DI+2] JE EXIT ;相等,找到,就是第一个数 STC ; 小于第一个数,失败 JMP EXIT CHK_LAST:MOV SI, ES:[DI] ;取数组长度 SHL SI, 1 ; 长度*2(DW型) ADD SI, DI CMP AX, ES:[SI] ; 与最后的数比较 JB SEARCH ; 小于则转 JE EXIT ; 相等则结束 STC ; 大于最后一个,失败
SEARCH: MOV LOW_IDX, 1 ; 给LOW赋初值 MOV BX, ES:[DI] ; 取数组长度 MOV HIGH_IDX, BX ; 给HIGH赋初值 MOV BX, DI ; BX中放首地址 MID: MOV CX, LOW_IDX MOV DX, HIGH_IDX CMP CX, DX JA NO_MATCH ; LOW>HIGH,失败 ADD CX, DX SHR CX, 1 ; 折半 MOV SI, CX SHL SI, 1 ; *2 (DW型) COMPARE:CMP AX, ES:[BX+SI] ; 与中间数比较 JE EXIT ; 相等,找到
JA HIGHER ; 大于中间数,转 DEC CX MOV HIGH_IDX, CX ; 调整查找区间到前半部分 JMP MID HIGHER: INC CX MOV LOW_IDX, CX ; 调整查找区间到后半部分 NO_MATCH: STC EXIT: ……
3.6 循环结构程序设计 任务需要重复执行某一程序段,这种情况采用循环结构来实现。 初始化 初始化 N 控制部分 循环体 Y 修改部分
3.6.1 循环指令 格式:指令码 标号; (CX中存放循环次数) 操作符 操 作 功 能 LOOP CX<--(CX)-1 循环 操作符 操 作 功 能 LOOP CX<--(CX)-1 循环 若(CX)<>0,则循环 LOOPZ CX<--(CX)-1 当CX不为零且 LOOPE 若(CX)<>0且ZF=1,则循环 相等时循环 LOOPNZ CX<--(CX)-1 当CX不为零且 LOOPNE 若(CX)<>0且ZF=0,则循环 不相等时循环
循环指令示例1 求长度为10的字节数组ARRAY之和,并将和存入TOTAL中 LEA SI, ARRAY ;数组首地址-->SI MOV CX, 10 ;数组长度-->CX MOV AX, 0 AGAIN: ADD AL, [SI] ;求数组和 ADC AH, 0 INC SI ;修改指针 LOOP AGAIN MOV TOTAL, AX ;存和 其中 语句 LOOP AGAIN 相当于: DEC CX JNZ AGAIN
循环指令示例2 在某一字节串中寻找第一个非0字节 设串首地址在DI中,串末地址在BX中 SUB BX, DI ;串长度在BX中 INC BX MOV CX, BX ; 串字节数-->CX DEC DI AGAIN: INC DI ;修改指针 CMP BYTE PTR [DI], 0 ;串元素=0? LOOPZ AGAIN ;循环查找 JNZ FOUND ;找到非0字节跳转 …… FOUND:
3.6.2 串操作指令 串——存储器中一序列字或字节单元,单元中的内 容是字符或数据 串操作——对序列字或字节单元中的内容进行某种 操作 串操作指令有7条: 1、MOVS——串传送指令 2、CMPS——串比较指令 3、SCAS——串扫描指令 4、LODS——装入串指令 5、STOS——存储串指令 6、INS——串输入 7、OUTS——串输出
说明: 每条指令有三种形式,分别对应于字节操作、 字操作和双字操作 如 MOVSB 字节操作 MOVSW 字操作 MOVSD 双字操作 与此配合使用的指令前缀有: REP 重复 REPE/REPZ 相等/为零则重复 REPNE/REPNZ 不相等/不为零则重复
例:将字节串从源区传送到目的区 源区首偏址-->SI 目的区首偏址-->DI,串长-->CX Y CX=0 N 按SI所指取一字节 结束 按DI所指存此字节 (SI)+1-->SI 用一般传送指令实现 的流程图 (DI)+1-->DI (CX)-1-->CX
源区首偏址-->SI 目的区首偏址-->DI 串长-->CX,0-->DF 源区首偏址-->SI CX=0 Y 源区首偏址-->SI 目的区首偏址-->DI 串长-->CX,0-->DF CX=0 N 串传送指令 结束 带前缀REP的 串传送指令 (CX)-1-->CX 用串传送指令实现 的流程图 用带前缀的串传送指令实现的流程图
使用串操作指令时微处理器设计有若干约定: 1、源串地址由DS:SI指定 目的串地址在ES:DI中 2、串长送CX寄存器 3、设置方向标志位DF(在EFLAG寄存器中) 当DF=0(指令CLD)时地址为增量修改 (+1 或 +2 或 +4) 当DF=1(指令STD)时地址为减量修改 (-1 或 –2 或 -4)
方向标志对应的指针移动示意 正向传送 反向传送 DF=0 DF=1 低地址方向 ‘A’ ... 源串 ‘A’ ‘J’ … 1 n 源串 n 高地址方向 1 n 源串 n 1 ‘J’ ... 目的串 … 目的串 正向传送 反向传送 DF=0 DF=1
MOVS 串传送 ES:DI<--(DS:SI) REP SI<--(SI)(+/-)1 DI<--(DI)(+/-)1 符号 功能 操作 相关前缀 MOVS 串传送 ES:DI<--(DS:SI) REP SI<--(SI)(+/-)1 DI<--(DI)(+/-)1 CMPS 串比较 (DS:SI)-(ES:DI) REPZ/REPNZ SCAS 串扫描 (ES:DI)-(AL) REPZ/REPNZ LODS 装入串 AL<--(DS:SI) 一般不联用 STOS 存入串 (ES:DI)<--(AL) REP
INS 串输入 ES:DI((DX)) REP DI(DI)(+/-)1 OUTS 串输出 ((DX))(DS:SI) REP 符号 功能 操作 相关前缀 INS 串输入 ES:DI((DX)) REP DI(DI)(+/-)1 OUTS 串输出 ((DX))(DS:SI) REP SI(SI)(+/-)1 其中DX寄存器中存放的是接口电路的端口号
REPZ CX=0 或 ZF=0 CX<--(CX)-1,继续 REPE SI,DI指向下一元素 串未结束且串相等时继续 重复前缀 终止条件 否则 REP CX=0 CX<--(CX)-1,继续 SI,DI指向下一元素 REPZ CX=0 或 ZF=0 CX<--(CX)-1,继续 REPE SI,DI指向下一元素 串未结束且串相等时继续 REPNZ CX=0 或 ZF=1 CX<--(CX)-1,继续 REPNE SI,DI指向下一元素 串未结束且串不相等 时继续
例:REP MOVSB 传送过程如下: (1)(CX)=0? 若等于0,中止传送, 否则执行下一步 (2)CX(CX)-1 (3)串传送 (4)修改指针 (5)转到(1)
MOVS指令示例 MOV DI, 0100H ; (ES)=3000H MOV CX, 5 CLD ; 地址递增方式 REP MOVSB MOV SI, 0050H ; (DS)=2000H MOV DI, 0100H ; (ES)=3000H MOV CX, 5 CLD ; 地址递增方式 REP MOVSB 执行前 执行后 ‘A’ 20050 00 30100 ‘A’ 20050 30100 ‘B’ 1 00 1 ‘B’ 1 1 ‘C’ 2 00 2 ‘C’ 2 2 ‘D’ 3 00 3 ‘D’ 3 3 ‘E’ 4 00 4 ‘E’ 4 4 ‘F’ 5 00 5 ‘F’ 5 5 ‘A’ ‘B’ ‘C’ ‘D’ ‘E’ 源区 目的区 源区 目的区 SI=0050 DI=0100 SI=0055 DI=0105
串String1和String2分别定义在数据段和附加段中。 比较两串,如相等则转移到标号NEXT处。 CMPS指令示例 串String1和String2分别定义在数据段和附加段中。 比较两串,如相等则转移到标号NEXT处。 String1 DB ‘HELP’ ;定义String1 String2 DB ‘HEPP’ ;定义String2 …… CLD ;DF=0 LEA SI, String1 ;源串地址-->SI LEA DI, String2 ;目的串地址-->DI MOV CX, 4 ;重复次数-->CX REPZ CMPSB ;重复比较 JZ NEXT ;串相等转移 .... NEXT: 之所以用JZ来判断,是由于,如果两个串不相等,则REPZ一定是在ZF=0的时候退出来的,如果串完全相等,则退出的时候ZF=1(因为最后一对也匹配)
在串“That is CAI”中查找字符‘a’,找到,则转到标号FOUND处 SCAS指令示例 在串“That is CAI”中查找字符‘a’,找到,则转到标号FOUND处 String DB ‘That is CAI’ ;定义串 …… CLD ;DF=0 LEA DI, String ;串地址-->DI MOV AL, ‘a’ ;查找字符-->AL MOV CX, 11 ;重复次数-->CX REPNZ SCASB ;重复扫描 JZ FOUND ;找到目的串元素转移 FOUND:
比较SOURCE和DESTIN (串长度为100个字节),并将串中的第一个不匹配元素装入AL寄存器中。 LODS指令示例 比较SOURCE和DESTIN (串长度为100个字节),并将串中的第一个不匹配元素装入AL寄存器中。 …… LEA SI, SOURCE ;源串偏移量-->SI LEA DI, DESTIN ;目的串偏移量-->DI CLD ;DF=0 MOV CX,100 ;重复比较次数-->CX REPZ CMPSB ;重复串比较 JCXZ MATCH ;没有不匹配元素跳转 DEC SI ;指向不匹配元素 LODSB ;装入不匹配元素到AL ... MATCH:
给首地址为BUF,长度为1000个字节的存储器区域清零。 STOS指令示例 给首地址为BUF,长度为1000个字节的存储器区域清零。 BUFF DB 1000 DUP(?) ;定义缓冲区 …… CLD ;DF=0 LEA DI, BUFF ;缓冲区首地址-->DI MOV CX, 1000 ;重复次数 MOV AL, 0 ;0-->AL REP STOSB ;重复存储串
将存储区A到A+i中的数据传送到存储区B到B+i中,要求存放的顺序与原先的顺序相反。 综合应用例1 将存储区A到A+i中的数据传送到存储区B到B+i中,要求存放的顺序与原先的顺序相反。 B +1 +2 ‘b’ B+i ‘a’ A ‘a’ +1 ‘b’ +2 ‘c’ A+i
LEA SI, A LEA DI, B ADD DI, I ;DI指向存储区B的末尾 MOV CX, I+1 ;串的长度 LP: CLD ;DF=0 LODSB ;从源区取一数据 STD ;DF=1,改变方向 STOSB ;存入目的区 DEC CX JNZ LP
3.6.3 循环结构程序设计 循环程序的组成: 1、初始化部分 设置初始值 2、循环工作部分 具体的操作和运算 1、初始化部分 设置初始值 2、循环工作部分 具体的操作和运算 3、循环修改部分 为执行下一循环而修改某些参数 4、循环控制部分 判断循环继续还是结束 循环控制方法有: (1)计数控制法 增数法 减数法 (2)条件控制法
单重循环程序设计 将以s1为起始地址的26个字母依次传送到以s2为起始地址的连续单元中。 数据定义如下: .DATA S1 DB ‘ABCD……XYZ’ .DATA ESTRA S2 DB 26 DUP(?)
方法1 采用寄存器间接寻址方式 MOV AX, SEG S1 ;初始化部分 MOV DS, AX MOV AX, SEG S2 MOV ES, AX MOV SI, OFFSET S1 MOV DI, OFFSET S2 MOV CX, 26 LOP1: MOV AL, [SI] ;工作部分 MOV ES:[DI], AL INC SI ;修改部分 INC DI LOOP LOP1 ;控制部分
方法2 采用串处理指令 MOV AX, SEG S1 ;初始化部分 MOV DS, AX MOV AX, SEG S2 MOV ES, AX LEA SI, S1 LEA DI, S2 MOV CX, 26 CLD REP MOVSB ;工作、修改、控制合为一条指令
计数控制法 计数控制法适用于循环次数已知的场合 1、增数法 初始化时循环计数器置0,每执行一次循环体后计数器加1,并与已知的循环次数比较,如相等则退出循环。 增数法一般用比较指令和条件转移指令实现循环转移。 2、减数法 初始化时循环计数器置为循环次数,每执行一 次循环体后计数器减1,并测试循环计数器是否为0, 如为0则终止循环。 减数法一般用循环指令形成循环回路。
如何做N阶乘N! 增数法 计算S=1+2+3+···+50, 结果存入AX中 NUM DW 1 ; ...... MOV CX,0 ;初始化 MOV AX,0 ROTATE: ADD AX, NUM ;累加 INC WORD PTR[NUM] INC CX ;计数器加1 CMP CX,50 ;与已知的循环次数比较 JNZ ROTATE MOV S, AX 如何做N阶乘N!
将内存中6个十进制数的ASCII码转换为非压缩BCD码,并存放在后继相应单元中,如错,存放0FFH。 减数法 将内存中6个十进制数的ASCII码转换为非压缩BCD码,并存放在后继相应单元中,如错,存放0FFH。 ASCBUF DB 35H, 38H, 30H, 4DH, 39H, 32H DB 6 DUP(?) ...... MOV DI OFFSET ASCBUF MOV CX, 6 LAB_1:MOV BL, 0FFH ; 设置错误标志 MOV AL, [DI] CMP AL, 3AH JNB OK ;大于等于3A则错 SUB AL, 30H JC OK ; 小于30H也错 MOV BL, AL OK: MOV AL, BL MOV [DI+06H], AL INC DI LOOP LAB1 ...... 十进制数字‘0’~‘9’的ASCII是30H~39H
条件控制法 在许多情况下,事先无法确定循环次数,这时可选用“条件”来控制循环。在问题的求解过程中,找出一个终止循环的条件。 每循环一次,对条件进行一次检测,如满足终止循环的条件,便退出循环,否则继续循环。 利用条件转移指令控制循环是否结束。 有些情况下为防止死循环,可以附加一个合适的循环次数。
条件控制法 求字符串长度。从STRN地址开始有一个字符串,以‘$’作为结束标志,长度不超过100个字节,要求统计该字符串长度并存于LENG单元。 分析:1)设DX存放统计的串长度,为防止程序死循环,可根据串长不超过100作为循环结束的附加条件。 2)如果程序运行过程中找到‘$’则正常退出循环,如找不到‘$’,由于CX的初值为100,故不会使程序死循环。 定义数据段如下: .DATA STRN DB ‘XCVFATTDEQJHI…’,’$’ LENG DB 0
…… MOV AX, 0 MOV DX, AX ; DX清零 LEA DI, STRN ; 串指针赋初值 MOV CX, 100 ; 初始值为100 MOV AL, ‘$’ LP: CMP AL, [DI] JE DONE ; 条件控制 INC DX ; 串长+1 INC DI ; 串指针后移一个字节 LOOP LP ; 附加条件 DONE: MOV LENG, DL; 存字符串长度
多重循环程序设计 多重循环就其本质而言,就是循环层1包含循环层2,循环层2可能又包含循环层3…,循环层1为最外层,内层循环是外层循环的循环体的一部分。
如果多重循环都使用LOOP语句,则要保护好外层循环的计数器CX,通常采用压栈或退栈的方法或者转存的方法。 多重循环程序设计
有一个首地址为A的N字数组,使该数组中的数从大到小排序 冒泡排序法 MOV CX, N DEC CX LOOP1: MOV DI, CX ; 暂存外循环计数值 MOV BX, 0 LOOP2: MOV AX, A[BX] CMP AX, A[BX+2] ;比较a(i)与a(i+1) JGE COTINUE XCHG AX, A[BX+2] ;交换 MOV A[BX], AX COTINUE: ADD BX, 2 LOOP LOOP2 MOV CX, DI ;恢复外循环计数值 LOOP LOOP1
3.7 子程序设计 子程序调用(返回)指令 过程定义伪指令 子程序的调用和返回 主程序与子程序的连接 子程序调用中的数据保护与恢复 主程序与子程序之间的参数传递 子程序嵌套与递归
3.7.1 子程序调用(返回)指令 1)CALL 子程序调用指令 格式1: CALL 子程序名 ;段内直接调用 操作1: SP<--(SP)-2 ((SP)+1,(SP))<--(IP) IP<--(IP)+disp16 格式2:CALL reg/mem ;段内间接调用 操作2:SP<--(SP)-2 ((SP)+1,(SP))<--(IP) IP<--(reg) 或 IP<--(mem) 说明:1)类似于段内无条件转移指令,不同的是需要将返回地址IP入栈 2)也可以在子程序名前使用near ptr 前缀
格式3:CALL FAR PTR 子程序名 ;段间直接调用 操作3:SP<--(SP)-2 ((SP)+1,(SP))<--(CS) SP<--(SP)-2 ((SP)+1,(SP))<--(IP) IP<--子程序名对应的偏移量 CS<--子程序所在的段地址
格式4:CALL DWORD PTR mem ;段间间接调用 操作4: SP<--(SP)-2 ((SP)+1,(SP))<--(CS) IP<--(SP)-2 ((SP)+1,(SP))<--(IP) IP<--(EA) CS<--(EA+2) 说明:段间调用须保存返回地址IP和CS 386及其后继机型,用EIP代替IP
2) RET 子程序返回指令 格式 功能 操作 RET 段内返回 IP<--((SP)+1,(SP)) SP<--(SP)+2 RET 段间返回 IP<--((SP)+1,(SP)) CS<--((SP)+1,(SP)) RET EXP 带立即数返回 在上述操作之后再做 SP<--(SP)+EXP
3.5.2 过程定义伪指令 格式:过程名 PROC 属性 ...; 过程体 RET 过程名 ENDP 说明: 1、过程名是该子程序名,也是指令CALL的目标操作数 2、过程的属性有两种:NEAR和FAR,分别表示段内调用和段间调用。若省略,则默认为NEAR 3、至少有一条RET指令从过程中返回,可在过程中的任何位置
3.5.3 子程序的调用和返回 主程序 子程序 SUB PROC . . CALL SUB . (断点) . RET
3.5.4 主程序与子程序的连接 (1)主程序和子程序在同一代码段内 .CODE C_SEG MAIN PROC FAR ;主程序 ... CALL SUB_A MAIN ENDP SUB_A PROC NEAR ;子程序 RET SUB_A ENDP END MAIN
(2)调用程序与子程序不在同一代码段内 ;模块1 ;模块2 .CODE C_SEG1 .CODE C_SEG2 …… …… ;模块1 ;模块2 .CODE C_SEG1 .CODE C_SEG2 …… …… MAIN PROC FAR SUB_1 PROC FAR …… …… CALL FAR PTR SUB_1 RET …… …… RET SUB_1 ENDP MAIN END END SUB_1 END MAIN
示例 C1段 MAIN …… CALL FAR PTR PRO_A 0500:1000 C2段 PRO_A CALL NEAR PTR PRO_B 2000:2500 CALL NEAR PTR PRO_C 2000:3700 …... RET PRO_B …… CALL NEAR PTR PRO_C 2000:4000 RET PRO_C 转1 转2 转3 68
(1) MAIN调用PRO_A之前 SP (3) PRO_A调用PRO_B之后 0100 2500 1000 0500 00FA 4000 (4) PRO_B调用PRO_C之后 SP 00F8 00FC 1000 0500 转 69
(5) PRO_C返回PRO_B之后 SP 4000 00FA 2500 (7) PRO_A调用PRO_C之后 1000 0500 SP (6) PRO_B返回PRO_A之后 00FA (7) PRO_A调用PRO_C之后 SP 00FA 4000 3700 1000 0500 00FC 转 70
(8) PRO_C返回PRO_A之后 SP 00FC 4000 3700 (9) PRO_A返回MAIN之后 1000 SP 0500 0100 转 71
使用DOS功能调用的4CH功能: MOV AH, 4CH INT 21H 利用程序段前缀退出程序的方法 使用DOS功能调用的4CH功能: MOV AH, 4CH INT 21H 使用INT 20H指令 利用程序段前缀法 将主程序设计成一个属性为FAR的过程,由DOS调用该过程执行,由RET指令返回DOS(利用程序段前缀的结构) 72
操作系统加载EXE程序时自动在程序前加上256字节的程序段前缀,DS指向它 INT 20H DS 程序段前缀区 使用程序段前缀 退出程序: 操作系统加载EXE程序时自动在程序前加上256字节的程序段前缀,DS指向它 INT 20H DS 程序段前缀区 程序区 PUSH DS MOV AX,0 PUSH AX 把程序段前缀区第一个字节单元的地址和偏移量压栈保存,这个单元是指令INT 20H,这几句一定要放在主程序开始的位置 RET 指令 把程序段前缀区第一个字节单元的地址和偏移量弹出分别送IP和CS,转去执行INT 20H,从而实现返回DOS 求助!!(旧坛转贴)这是一个实现5+2=7功能的程序,请帮忙看一下,有几个问题想向大家请教!!谢谢了!! 1----------SAMPLE PROGRAM FOR ADD AND DISPLAYING SUM TO THE SCREEN 2----------DATA SEGMENT ;数据段 3----------AUGEND DB 05H 4----------ADDEND DB 02H 5----------SUM DB ? 6----------DATA ENDS 7----------STACK SEGMENT PARA STACK 'STACK' ;堆栈段 8---------- DB 64 DUP(?) 9----------STACK ENDS 10---------CODE SEGMENT ;代码段 11---------START PROC FAR 12--------- ASSUME CS:CODE,DS:DATA,SS:STACK,ES:DATA 13--------- PUSH DS ;保存返回地址 14--------- MOV AX,0 15--------- PUSH AX 16--------- MOV AX,DATA ;初始化DS,ES 17--------- MOV DS,AX 18--------- MOV ES,AX 19--------- MOV AL,AUGEND ;完成05H+02H的程序正文 20--------- ADD AL,ADDEND 21--------- MOV SUM,AL ;存结果 22--------- ADD AL,30H ;将结果变为ASCII码 23--------- MOV DL,AL ;显示结果 24--------- MOV AH,02H 25--------- INT 21H 26--------- RET 27---------START ENDP 28---------CODE ENDS 29--------- END START ;汇编结束 我想问的是14行为什么将AX置零呀,还有23行和24行是什么意思呀,非常感谢您的解答。 发贴时间: 2004-3-25 16:47:04 221.8.*.* ================================================ 首先回答你的第一个问题: 这个问题说起来比较复杂一点。首先先要介绍一下DOS可执行程序的结构,我们知道,DOS执行文件通常有代码段,数据段,堆栈段,有的还有附加段,当DOS可执行程序被装入到内存的时候,DOS会在可执行程序的前面加上一个256字节的数据结构,这个结构称为PSP(Program Segment Prefix, 程序段前缀控制块。该块的结构如下: 偏移量 长度 描述 00H 1 WORD 指令 INT 20H 返回到DOS 02H 1 WORD 程序分配块的底部 04H 1 BYTE 保留 05H 5 BYTE CALL功能调用入口 0AH 2 WORD INT 22H结束地址 0EH 2 WORD INT 23H CTRL-BREAK处理程序地址 12H 2 WORD INT 24H 标准错误处理程序地址 16H 1 WORD 父进程PSP 18H 20 BYTE 句柄表 2CH 1 WORD 环境块地址 2EH 2 WORD 保留 32H 1 WORD 句柄表大小 34H 2 WORD 句柄表地址 38H 24 BYTE 保留 50H 1 WORD INT 21H DOS 调用 52H 1 BYTE FAR RET 53H 9 BYTE 保留 5CH 16 BYTE 为未打开的FCB1使用 6CH 20 BYTE 为未打开的FCB2使用 80H 1 BYTE 命令行参数的长度 81H 127 BYTE 命令行参数 程序真正的代码就紧接在该PSP之后,既从100H开始。程序调入后,CS和SS会自动指到代码段和堆栈段。但DS和ES并不指向数据段和附加段,而是指向PSP段地址(所以我们程序里都会根据实际需要加上16,17,18行来明确地为DS和ES赋值,使他们指向数据段或必要的时候指向附加段)。 现在我们来看看26行,这是一个RET语句,我们知道,RET语句会执行以下操作 IP<-SS:[SP] SP<-SP+2 CS<-SS:[SP] 从而实现过程返回,返回的地址是CS:IP。这是整个程序的结束语句,但是在整个程序的开头以及程序当中并没有CALL这样的语句和这里的RET配对,显然堆栈中就不会有要返回的CS:IP值,这样执行到这句就会出错。而13,14,15三行,正是在人为构造这个返回的CS:IP值,首先讲DS压栈,从前面的分析我们知道,DS目前的值是指向PSP,而AX中是0,所以15行显然是将0压入堆栈。这样一来,26行的RET语句就使得CS:IP=PSP:0000H,我们再来看PSP:0000H的位置放的是INT 20H指令,该指令的功能是返回到DOS,显然就实现了程序的结束,并返回DOS。 下面看第二个问题,这个问题很简单,注意24, 25行是要调用DOS功能调用INT 21H 的02H号功能(DOS功能调用需要将功能号放到AH中所以有24行的语句),02H号功能是写标准输出设备(主要是显示器),该号功能要求将要显示的字符的ASCII码放到DL寄存器中,23行语句就是做这件事情的。 发贴时间: 2004-3-25 19:27:08 218.2.*.* 程序加载结构
存储单元NUM中为一个16位的二进数,统计其中值为1 的位的个数存入RESULT .CODE ...… MAIN PROC FAR JZ DONE PUSH DS SAL AX, 1 MOV AX,0 JNC NEXT PUSH AX INC CL MOV AX, @D_SEG NEXT: JMP LOOP1 MOV DS, AX DONE: MOV RESULT, CL MOV CX,0 RET MOV AX, NUM MAIN ENDP LOOP1: AND AX, AX END MAIN 本程序采用的是用RET返回DOS的方法 循环结束条件是什么?采用这样的条件有什么好处?
3.5.5 子程序调用中的数据保护与恢复 为避免在主程序和子程序中使用相同的寄存器而引起的冲突,需要在子程序中对这些寄存器进行保护。在子程序返回前,恢复这些寄存器原来的值。 保护和恢复现场最佳的办法就是利用堆栈。 以下就是一个保护和恢复现场的例子: SUBT PROC NEAR PUSH AX ;保护现场 PUSH BX PUSH CX …… POP CX ;恢复现场 POP BX POP AX ; 注意弹出的顺序与压栈顺序相反 RET SUBT ENDP
教材上的例子请看P103的例3.21 定义一个过程,它的功能是将AL中的组合BCD码转为ASCII码,再存入BX寻址的连续的两个内存单元中。 TRAN PROC FAR PUSH AX PUSH BX PUSH CX PUSH DX MOV DL, AL MOV CL, 4 SHR AL, CL OR AL, 30H MOV [BX], AL INC BX MOV AL, DL AND AL, 0FH OR AL, 30H MOV [BX], AL POP DX POP CX POP BX POP AX RET TRAN ENDP 教材上的例子请看P103的例3.21
3.5.6 主程序与子程序之间的参数传递 参数传递的方法一般有三种: (1) 寄存器传送 将入口参数和出口参数放在约定的寄存器中 (1) 寄存器传送 将入口参数和出口参数放在约定的寄存器中 适用于参数个数较少的情况 (2) 存储单元传递 有直接存储单元传递和地址表传递两种方法 (3) 利用堆栈传递参数 应注意避免破坏断点
寄存器传递参数示例1 十进制到十六进制数转换。从键盘取得一个十进制数,将其以十六进数形式显示出来。 从键盘取得十进制数(小于65536),保存在BX 调用DECIBIN 调用CRLF 显示回车和换行 调用BINIHEX 用十六进制形式显示BX中的数 调用CRLF
主程序: MAIN PROC FAR REPEAT: CALL DECIBIN CALL CRLF CALL BINIHEX JMP REPEAT MAIN ENDP
DECIBIN PROC NEAR ;出口参数BX PUSH AX ;保护现场 PUSH CX MOV BX, 0 NEWCHAR: MOV AH, 1 INT 21H ;读键盘,输入的ASCII在AL中 SUB AL, 30H JL EXIT ;小于0转 CMP AL, 9 JG EXIT ;大于9转 CBW ; 字节转成字 XCHG AX, BX MOV CX, 10 MUL CX ; 将以前的值乘以10 ADD BX, AX ; 加这一次读的值 JMP NEWCHAR EXIT: POP CX ;恢复现场 POP AX RET DECIBIN ENDP 功能:从键盘取得十进制数,保存在BX
功能:用十六进制形式显示BX中的数 BINIHEX PROC NEAR ;入口参数BX …… MOV CH, 4 ;循环4次(BX中有4位16进制数) ROTATE: MOV CL, 4 ROL BX, CL ; 循环左移四位,从最高位输出 MOV AL, BL AND AL, 0FH ADD AL, 30H CMP AL, 3AH JL PRINTIT ;是0--9 ADD AL, 7 ;是A--F PRINTIT: MOV DL, AL MOV AH, 2 INT 21H ;显示(输出)DL中的一个字符 DEC CH JNZ ROTATE RET BINIHEX ENDP 功能:用十六进制形式显示BX中的数
功能:显示回车和换行 CRLF PROC NEAR PUSH AX PUSH DX MOV DL, 0DH ;回车 MOV AH, 2 INT 21H ; 输出DL中的字符 MOV DL, 0AH ; 换行 POP DX POP AX RET CRLF ENDP
寄存器传递参数示例2 求数组元素之和。 .DATA ARRAY DB 10,20,30,5,60 COUNT EQU $-ARRAY ;数组元素个数 .STACK 100H .CODE START: ... LEA SI, ARRAY ;参数准备 MOV CX, COUNT CALL SUM1 ;求和 ……
;子程序:SUM1 ;入口参数:SI=数组首址,CX=数组长度 ;出口参数:AX=数组和 ;使用寄存器:AX, CX, SI SUM1 PROC NEAR …… CMP CX,0 JZ EXIT XOR AX, AX AGAIN: ADD AL, [SI] ADC AH,0 INC SI LOOP AGAIN EXIT: …… RET SUM1 ENDP
直接存储单元传送示例 求数组元素之和,结果送SUM单元 .DATA ARRAY DW 100 DUP(?) COUNT DW 100 SUM DW ? .CODE ...... CALL PROADD ;主程序
PROADD PROC PUSH AX ;保存现场 PUSH CX PUSH SI XOR AX, AX LEA SI, ARRAY ;直接使用存储单元中数据 MOV CX, COUNT NEXT: ADD AX, [SI] ADD SI, 2 LOOP NEXT MOV SUM, AX ;结果直接送SUM POP SI ;恢复现场 POP CX POP AX RET PROADD ENDP
地址表传递示例 调用子程序前,把所有参数的地址送入地址表,然后把地址表的偏移量通过寄存器带进子程序,子程序从地址表中取得参数地址。 求数组之和 .DATA ARRAY DW 50 DUP(?) COUNT DW 50 SUM DW ? TABLE DW 3 DUP(?) ...... MOV TABLE, OFFSET ARRAY MOV TABLE+2, OFFSET COUNT MOV TABLE+4, OFFSET SUM TABLE ARRAY首址 LEA BX, TABLE +2 COUNT地址 CALL PROADD +4 SUM地址
PROADD PROC ;入口参数 BX为地址表首地址 PUSHA ;保护现场 MOV SI, [BX] ;数组首地址送SI MOV DI, [BX+2] ;数组长度单元地址送DI MOV CX, [DI] ;数组长度送CX MOV DI, [BX+4] ;存储和的单元地址送DI MOV AX, 0 ADDT: ADD AX, [SI] ADD SI, 2 LOOP ADDT MOV [DI], AX POPA ;恢复现场 RET PROADD ENDP
利用堆栈传递参数示例——求数组之和 主程序中: LEA BX, ARRAY ;参数进栈 PUSH BX LEA BX, COUNT LEA BX, SUM CALL FAR PTR PROADD ...... PROADD PROC FAR PUSH BP ;保护现场 MOV BP, SP PUSH AX PUSH CX PUSH SI PUSH DI (DI) (SI) 注意,一共3个参数进栈,每个占2字节 (CX) (AX) 原始(BP) 新BP (IP) (CS) SUM地址 COUNT地址 ARRAY地址 高地址
MOV SI, [BP+10] ;取得参数地址 MOV DI, [BP+8] MOV CX, [DI] MOV DI, [BP+6] MOV AX, 0 ADDT: ADD AX, [SI] ADD SI,2 LOOP ADDT MOV [DI],AX ;保存结果 POP DI ;恢复现场 POP SI POP CX POP AX POP BP RET 6; 弹出压栈的6字节参数 PROADD ENDP SP (DI) (SI) (CX) (AX) BP 原始(BP) BP+2 (IP) BP+4 (CS) BP+6 SUM地址 BP+8 COUNT地址 BP+10 ARRAY地址 高地址
子程序嵌套 程序设计中,子程序A调用子程序B,子程序B 调用子程序C......这一过程称为子程序嵌套。 CALL CALL CALL RET RET RET 主程序 子程序1 子程序2 子程序N 注意: 1、每一层调用都要注意寄存器的保存和恢复 2、PUSH ,POP 的使用要严格平衡,确保自内向外顺序退出
当一个程序直接或间接调用其自身时,称为递归调用. 递归子程序示例——阶乘函数的计算 ...... N DB 4 RESULT DW ? 主程序: MOV AL, N CALL FACT ADD1: MOV RESULT, DX Y N n=0 1-->F n进栈 求出基数返回 n-1-->n 递归调用 弹出n n*F-->F 递归返回
FACT PROC ;入口参数AL(N的值) ,出口参数N!=DX CMP AL, 0 ;N=0? JNZ F1 ;N<>0跳转 MOV DX, 1 ;(DX)<--1 RET F1: PUSH AX ;N进栈 DEC AL ;(AL)<--N-1 CALL FACT ;递归 ADD2: POP CX ;(CX)<--N CALL MULT ;(DX)<--N*FACT(N-1) FACT ENDP MULT PROC ; 入口CL中是N,DX中是FACT(N-1) MOV AL, CL ; 出口参数DX中是N*FACT(N-1) MUL DL MOV DX, AX MULT ENDP