16位汇编语言学习笔记(2)—— 汇编程序设计
文章目录
- 4. 顺序程序设计
- 4.1 十进制的算数运算
- 4.2 输入输出功能调用
- 4.3 综合案例
- 5. 分支程序设计
- 5.1 转移指令
- 5.1.1 条件转移指令
- 单标志条件转移指令
- 无符号数专用条件转移指令
- 有符号数专用条件转移指令
- 5.1.2 无条件转移指令
- 配合条件转移指令实现条件远转移
- 避免一个程序分支滑入另一个程序分支
- 实现多分支程序结构
- 5.2 分支程序设计
- 5.2.1 测试法分支程序设计
- 5.2.2 跳转表法分支程序设计
- 5.3 分支程序综合举例
- 6. 循环程序设计
- 6.1 循环指令
- 6.2 串操作指令及重复前缀
- 6.3 循环程序设计
- 6.3.1 计数控制的循环程序设计
- 6.3.2 条件控制的循环程序设计
- 6.3.3 多重循环程序设计
- 6.4 循环程序综合设计举例
- 7. 子程序的设计和系统调用
- 7.1 调用和返回指令
- 7.2 子程序的设计
- 7.2.1 子程序的定义
- 7.2.2 子程序的调用和返回
- 7.2.3 保护现场和恢复现场
- 7.2.4 参数的传递
- 7.3程序的嵌套和递归
- 7.3.1 子程序的嵌套
- 7.3.2 子程序的递归
- 7.4 子程序调用和系统功能调用
- 7.4.1 系统功能调用的方法
- 7.4.2 子程序设计综合实例
- 参考
4. 顺序程序设计
4.1 十进制的算数运算
汇编语言中数字后是否要加H
debug和编译器对此的要求是不同的
我们在编写.asm文件时,不加H默认为10进制,加H表示为16进制,也有些编译器要求以0x开头表示16进制
而debug中默认所有数字都是16进制。比如用A命令输入mov ax,100a,不用加H,否则出错
在将.asm文件编译连接之后,使用debug对程序的执行进行跟踪时,会将源程序中的mov ax,10变为mov ax,000A
BCD 码:十进制数的二进制编码
- 非压缩BCD码
非压缩BCD码以8个二进制数码表示一个十进制数码,实际上是以低4位表示十进制数码,而高4位无意义。
十进制8901的非压缩BCD码为
- 压缩BCD码
压缩BCD码以4个二进制数码表示一个十进制数码。一个十进制数可表示为一个顺序排列,8位二进制数码为一组的二进制数字串。
十进制8901的压缩BCD码为
- BCD码调整指令和十进制算数运算
所谓十进制算术运算,是指参加算术运算的操作数是BCD码,结果也是BCD码形式。80X86系统中没有设置专门的十进制算术运算指令,而是通过二进制算术运算指令辅之以BCD码调整指令来实现十进制算术运算。
- 非压缩BCD码加法调整指令
格式: AAA
功能:在AAA指令前,应该已经使用ADD、ADC或INC指令,且用AL存放二进制加法之和。AAA指令将AL中的和调整为非压缩BCD码并送AX。
例: DATA DB 05H,08H
MOV AH,0
MOV AL, DATA
ADD AL, DATA+1; AL=0DH
AAA; (AX)=0103H, CF=1;
例: DATA DB 09H, 08H
MOV AH,0
MOV AL,DATA
ADD AL,DATA+1; (AL)=11H
AAA; (AX)=0107H, CF=1
例: DATA DB 02H,06H
MOV AH,0
MOV AL,DATA
ADD AL,DATA+1; (AL) =08H
AAA; (AX)=0008H,CF=0;
非压缩BCD码减法调整指令
格式:AAS
功能:在AAS指令前,应该已经使用SUB、SBB或DEC指令,且用AL存放二进制减法之差。AAS指令将AL中的差调整为非压缩BCD码并送回AL,向高位的借位在AH和CF中。非压缩BCD码乘法调整指令
格式:AAM
功能:在AAM指令前,应该已经使用MUL指令,且用AX存放二进制乘法之积。AAM指令将AX中的积调整为非压缩BCD码并送回AX。非压缩BCD码除法调整指令
格式:AAD
功能:该指令用在除法指令DIV之前。该指令对AX中的两位非压缩BCD码被除数进行调整,以使其后的DIV指令所得到的商(AL)为非压缩BCD码。压缩BCD码加法调整指令
格式:DAA
功能:在DAA指令前,应该已经使用ADD、ADC或INC指令,且用AL存放二进制加法之和。DAA指令将AL中的和调整为压缩BCD码并送回AL,向高位的进位在CF中。压缩BCD码减法调整指令
格式:DAS
功能:在DAS指令前,应该已经使用SUB、SBBA或DEC指令,且用AL存放二进制减法之差。DAS指令将AL中的差调整为压缩BCD码并送回AL,向高位的借位在CF中。
例:
DATA DB 42H, 17H
MOV AL,DATA; (AL)=42H
SUB AL,DATA+1; (AL)=2BH
DAS; (AL)=25H,CF=0
4.2 输入输出功能调用
输入输出DOS功能调用的使用方法
输入/输出DOS功能是通过服务子程序来实现的。DOS对系统功能的服务子程序进行了编号,简称为功能号。某个输入/输出DOS功能的调用,实际上就是使程序转向对应的服务子程序。
在源程序中进行输入/输出DOS功能调用的一般步骤为:
(1)为服务子程序设置入口参数。入口参数的设置一般借助通用寄存器。少数服务子程序无须入口参数,当然就不需要这一步骤。
(2)将功能号送AH。
(3)使用INT 21H指令。
该指令称为软中断指令,其功能是根据AH中的功能号转向对应的服务子程序的入口。有些服务子程序执行后还会通过通用寄存器带回出口参数。常用输入/输出功能调用
- 01H
功能:等待从标准设备(如键盘)输入一个字符,将该字符的ASCII码送AL,并在标准输出设备(如屏幕)上显示该字符。
入口参数:无
出口参数:AL的内容为输入字符的ASCII码。说明:当输入的字符为Ctrl+Break时,终止程序的执行。
MOV AH,01H
INT 21H
- 02H
功能:将DL中的一个字符显示在标准输出设备(如屏幕)上。入口参数:DL的内容设置为待显示字符的ASCII码。
出口参数:无。
说明:当DL中的字符为Ctrl+Break时,终止程序的执行。
MOV DL,待显示字符的ASCII
MOV AH,02H
INT 21H
- 07H
功能:等待标准输入设备(如键盘)输入一个字符,将该字符的ASCII码送AL。
入口参数:无。
出口参数:AL的内容为输入字符的ASCII码。
说明:该功能与01H号功能类似,区别有二:一是不显示输入的字符,二是当输入的字符为Ctrl+Break时,并不终止程序的执行。不显示输入的字符这一特点适用于输入保密用的口令。
MOV AH,07H
INT 21H
- 08H
该功能与07H号功能类似,区别仅在于,当输入的字符为Ctrl+Break时会终止程序的执行。 - 09H
功能:在标准输出设备上显示某个字符串。
入口参数:DX的内容为要显示的字符串的首地址。
出口参数:无。说明:确切地说,DX的内容应该是要显示的字符串的首地址的偏移地址,段地址由DS提供。要显示的字符串必须以‘$’作为结束标志。
MOV DX, 要显示的字符串的首地址
MOV AH,09H
INT 21H
- OAH
功能:将标准输入设备(如键盘)上输入的一串字符送到指定的内存缓冲区。
入口参数:DX放有内存缓冲区的首地址。
出口参数:无。说明:确切地说,DX放有内存缓冲区首地址的偏移地址,段地址由DS提供。输入的一串字符以回车符作为结束标志。输入的各字符按地址由小到大的次序存入DS: DX指定的内存缓冲区。
注意的是,在该功能调用之前,应该在存储缓冲区的第一个字节设置待接受字符的个数(范围为1~255)。存储缓冲区的第二个字节在该功能调用后被自动置上实际输入的字符数(回车符除外)。输入的字符从该存储缓冲区的第三个字节开始存放。若实际输入的字符数少于所设置的待接受字符的个数,则多余字节内容被置为零;若实际输入的字符数多于所设置的待接受字符的个数,则多余部分被忽略。
MOV DX, 缓冲区的首地址
MOV AH,0AH
INT 21H
- 4CH
功能:返回DOS。
入口参数:无。
出口参数:无。
说明:该功能调用常用于汇编源程序代码段的末尾处,使程序返回DOS。若需要用DEBUG等调试工具检查程序执行结果,如检查某些寄存器或内存单元的内容,则应该在程序执行至该功能调用之前进行。
MOV AH, 4CH
INT 21H
4.3 综合案例
【例】以下程序段用以显示信息“Press anykey when you ready.”。在用户按下任一键后,另起一行显示信息“Input your password:”。
DSEG SEGMENTMESS1 DB'Press any key when you are ready', OAH,ODH,'$'MESS2 DB'Input your password:' , OAH,ODH,'$'
DSEG ENDSSSEG SEGMENT STACK DB 80H DUP(0)
SSEG ENDSCSEG SEGMENT ASSUME DS:DSEG,SS:SSEG,CS,CSEGSTART: MOV AX, DSEGMOV DS,AXMOV DX,OFFESET,MESS1MOV AH,09HINT 21HMOV AH,08HINT 21HMOV DX,OFFSET MESS2MOV AH,09HINT 21H
CSEG ENDSEND START
【例】求S=a∗b−c2S=a*b-c^2S=a∗b−c2 , a=84, b=50,c=22
NAME AREA
DSEG SEGMENT
A DB 84
B DB 50
C DB 22
S DW ?
DSEG ENDS;SSEG SEGMENT STACKDB 80H DUP(0)
SSEG ENDS;CSEG SEGMENTASSUME DS:DSEG,SS:SSEG,CS:CSEGSTART: MOV AX,DSEGMOV DS,AXMOV AL,CMUL ALMOV DX,AXMOV AL,AMUL BSUB AX,DXMOV S,AXMOV AH,4CHINT 21H
CSEG ENDSEND START
【例】根据键盘输入的0~9中任一数字,计算出该数字的平方值,并以非压缩BCD码的形式存放于AX。AH存放十位数,而AL存放个位数。
DSEG SEGMENTDSEG ENDS;SSEG SEGMENT STACK DB 80H DUP(0)
SSEG ENDS;CSEG SEGMENTASSUME DS:DSEG,SS:SSEG,CS:CSEGSTART: MOV AH,1INT 21H; AL<- 输入的数字字符,如8AND AL,0FHMUL AL; AX<-8*8 即40HAAM; AX<-0604HMOV AH,4CHINT 21H
CSEG ENDSEND START
5. 分支程序设计
5.1 转移指令
5.1.1 条件转移指令
条件转移指令的一般格式为:JXX 标号
单标志条件转移指令
该类指令根据前一影响标志的指令执行后的结果是否为0,有没有进位,有没有溢出等情况确定程序的流向。
【例1】设AL=76H
CMP AL,80H
JNZ LAB2; AL-80H!=0的条件成立,即ZF=0, 转LAB2处
【例2】设BL=38H
SHL BL,1; BL 左移一位
JC LAB2; CF=1 的条件不成立,顺序执行LAB1处的指令
无符号数专用条件转移指令
该类指令根据两个无符号数的比较结果确定程序的流向。
【例】
设AL=76H
CMP AL,80H
JB LAB2; 无符号数76H低于无符号数80H的条件成立,转LAB2处。
【例】
设AL=76H
CMP AL, 80H
JAE LAB1; 无符号数76H高于等于无符号数80H的条件不成立,故顺序执行LAB2处的指令
有符号数专用条件转移指令
该类指令根据两个有符号数(用补码表示)的比较结果确定程序的流向。
【例】
设AL=76H
CMP AL, 80H
JL LAB2; 有符号数76H小于有符号数80H的条件不成立,故顺序执行LAB1处的指令
5.1.2 无条件转移指令
无条件转移指令的一般格式为JMP 目的地址
配合条件转移指令实现条件远转移
【例】 若条件转移指令指定的标号LAB2与该条件转移指令不在同一代码段,为了解决这个问题可将程序改为:
CMP AL,80H
JAE LAB1
JMP LAB2
LAB1:…
…
LAB2: …
避免一个程序分支滑入另一个程序分支
【例】 将有符号数X和Y中的较大者送变量Z。
X DB 60H
Y DB 94H
Z DB ?
…
MOV AL,X
CMP AL,Y
JL YG; AL 小于Y则转至YG处
MOV Z,AL
JMP SHORT OK
YG:MOV BL,Y
MOV Z,BL
OK:…
实现多分支程序结构
【例1】 设有如下的程序段: 当BX=0,2,4…,2N时,指令JMP BASE[BX] 分别使程序转向ZERO,ONE,TWO,…,N处
说明:
- 近转移的实质在于,改变IP总是指向当前代码段中的下一顺序指令的规律,而指向目的位置处的指令,即把目的地址的偏移地址送IP。 远转移不仅要完成这一个工作,还要把目的地址的段指令送CS.
- 近转移指令一般用于向下转移,即目的地址的偏移地址大于本转移指令位置的偏移地址,有时也用于向上转移。
- 转移指令本身不影响状态标志
- 在需要根据两个数的比较结果确定程序流向时,应根据题目设计的数据是无符号数还是有符号数来选择对应的条件转移指令。
5.2 分支程序设计
5.2.1 测试法分支程序设计
【例1】编写程序,求Z=|X-Y|
方法1:将有符号数X与Y相比较,若X<Y则将Y-X的值送Z;否则将X-Y的值送Z
DSEG SEGMENT
X DB 40H
Y DB 73H
Z DB ?
DSEG ENDS;SSEG SEGMENT STACK DB 80H DUP(0)
SSEG ENDS;CSEG SEGMENTASSUME DS:DSEG,SS:SSEG,CS:CSEGSTART: MOV AX,DSEGMOV DS,AXMOV AL,XCMP AL,Y; 根据X-Y产生状态标志JL XL; 有符号数X<Y 则转XLSUB AL,Y MOV Z, AL; Z<- X-YXL:MOV BL,YSUB BL,ALMOV Z,BL;Z<- Y-XOK:MOV AH,4CHINT 21H; 返回DOS
CSEG ENDSEND START
方法2:计算X-Y,若X-Y≥0,则直接取其差值;否则将差值取负。
DSEG SEGMENT
X DB 40H
Y DB 73H
Z DB ?
DSEG ENDS;SSEG SEGMENT STACK DB 80H DUP(0)
SSEG ENDS;CSEG SEGMENTASSUME DS:DSEG,SS:SSEG,CS:CSEGSTART:MOV AX,DSEGMOV DS, AXMOV AL,XSUB AL,Y; AL<- X-Y 且产生状态标志JNS XG; 差值为正则转XG处NEG ALXG: MOV Z,ALOK: MOV AH,4CHINT 21H; 返回DOS
CSEG ENDSEND START
【例2】根据键盘输入的数字0,1,2或3分别显示信息“8086/8088”,“80386”,“80486”或“Pentium”
DSEG SEGMENTMESS0 DB '808/8080','$'MESS1 DB '80386','$'MESS2 DB '80486','$'MESS3 DB 'Pentium','$'
DSEG ENDS;SSEG SEGMENT STACK DB 80H DUP(0)
SSEG ENDS;CSEG SEGMENTASSUME DS:DSEG,SS:SSEG,CS:CSEGSTART:MOV AX,DSEGMOV DS,AXMOV AH,8INT 21H; 从键盘输入一个字符,其ASCII码送ALAND AL,0FH; 将AL中的'0','1','2','3'转换成对应的数字0,1,2,3CMP AL,0JZ M0; AL=0 则转M0处CMP AL,1JZ M1;AL=1 则转M1处CMP AL,2JZ M2; AL=2 则转M2处M3:MOV DX,OFFSET MESS3JMP SHORT DISP M0: MOV DX,OFFSET MESS0JMP SHORT DISPM1:MOV DX,OFFSET MESS1JMP SHORT DISPM2: MOV DX,OFFSET MESS2DISP:MOV AH,09HINT 21H; 显示DX所指的字符串MOV AH,4CHINT 21H; 返回DOSCSEG ENDSEND START
5.2.2 跳转表法分支程序设计
跳转表是一段连续的存储区,根据其内容可以分为分支地址表和转移指令表两种,由此对应两种跳转表法分支程序设计。
设计思想: 通过DW指令将n个分支入口处的偏移地址如BRiBR_iBRi 按顺序存放在一段连续的存储区中,构成跳转表。设该区的首地址为BASE, 第i个分支入口的偏移地址的存放位置为BASE +2i 。当程序需要转向第i个分支时,只需将2i送BX,并使用指令JMPBASE[BX]JMP BASE[BX]JMPBASE[BX]
【例】 根据键盘输入的数字0,1,2或3,分别显示信息“8086/8088”,“80386”,“80486”或“Pentium”。要求配合使用分支地址构成的跳转表实现多分支程序设计。
此题要额外注意,键盘中输入的数字是以ASCII码的形式存储。
DSEG SEGMENT
BASE ;多分支入口处偏移地址构成跳转表DW M0DW M2DW M3
MESSO DB '8086/8088','$'
MESS1 DB '80386','$'
MESS2 DB '80486','$'
MESS3 DB 'Pentium','$'
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXMOV AH,8INT 21H;从键盘输入0,1,2,3 其ASCII码送入ALAND AL,0FH; 将输入的0,1,2,3 转换成对应的数字iSHL AL,1; AL<-2*iCBWMOV BX,AX;BX<-2*iJMP BASE[BX]; 利用地址表实现多分支M0:MOV DX,OFFSET MESS0JMP SHORT DISPM1:MOV DX,OFFSET MESS1JMP SHORT DISPM2:MOV DX,OFFSET MESS2JMP SHORT DISPM3:MOV DX,OFFSET MESS3;DISP:MOV AH,09HINT 21H; 显示DX所指的字符串
CSEG ENDSENDS START
5.3 分支程序综合举例
【例1】将BLKS为首址的连续N个字节数传送至以BLKD为首址的存储区,编写此数据块传送程序。
分析:
① 两数据块不重叠,在这种情况下从首部或从尾部开始传送均可以。② 两数据块有部分重叠,且BLKS地址大于BLKD地址。在这种情况下,只能从首部开始传送。若从尾部开始传送,则将破坏BLKS数据块中尚未传送的首部数据。
③ 两数据块有部分重叠,且BLKS地址小于BLKD地址。这种情况下,只能从尾部开始传送。
DSEG SEGMENTORG $+24STRG DB THIS IS A PROGRAM FOR STRING MOVING'N EQU $-STRGBLKS DW STRGBLKD DW STRG+5
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX, DSEG MOV DX,AXMOV CX,N; CX<-数据块字节数MOV SI, BLKS; SI 指向源数据块首部MOV DI,BLKD;DI 指向目的数据块首部MOV BX,1;设置SI,DI修正量为1CMP SI,DIJA MOVE ;源数据地址大于目的地址则转至MOVE处ADD SI,CXDEC SI; SI 指向源数据块尾部ADD DI,CX; DI 指向目的数据块尾部DEC DINEG BX; 设置SI,DI 的修正量为-1;MOVE:MOV AL,[SI]MOV [DI],ALADD SI,BXADD DI,BXDEC CXJNZ MOVE;CX 不等于0,传送尚未完毕,转至MOVE处继续传送MOV AH,4CHINT 21H; 返回DOS
CSEG ENDS
ENDS START
【例2】用汇编语言源程序实现以下C语言的switch语句的功能,语句中变量a和b为有符号整型(int)变量。
switch(a%8)
{case 0: b=32;break;case1:case2: b=a+43;break;case3:b=2*a;break;case 4:b--;break;case 5:case 6:case 7:printf("function5_6_7");break;
}
分析: 将多个分支程序入口地址CASE0、CASE12、CASE12、CASE3、CASE4、CASE567、CASE567及CASE567依次放在基地址为TAB的跳转表中。根据a除以8的余数,计算出对应的分支入口地址相对BASE的偏移量,然后用一条无条件转移指令即可转至对应的分支入口。程序如下:
DSEG SEGMENTA DW ?B DW ?TAB DW CASE0, CASE12,CASE12,CASE3DW CASE4, CASE567, CASE567,CASE567MSG DB 'function_5_6_7$'
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX, DSEGMOV DS, AXMOV AX, AMOV BX, AXAND BX, 7;得到BX的低3位,实现a%8的计算SHL BX,1JMP TAB[BX];利用地址表实现多分支CASE0:MOV B,32DJMP NEXTCASE12: ADD AX,43DMOV B,AXJMP NEXTCASE3:SHL AX,1MOV B,AXJMP NEXTCASE4:DEC BJMP NEXTCASE567:LEA DX,MSGMOV AH,9INT 21HNEXT:MOV AH,4CHINT 21H;返回DOS
CSEG ENDSENDS START
6. 循环程序设计
6.1 循环指令
重复控制指令的功能:
将寄存器CX默认为重复控制计数器,根据CX是否为0等情况来控制是转向段标号还是顺序执行。
重复控制指令的一般格式:xxx 短标号
- LOOP
功能:该指令首先使寄存器CX中的计数值减1,然后判断CX是否为0。若CX不等于0,则重复执行短标号开始的程序段,否则顺序执行Loop指令后的指令。
【例】求BUFF数据区中各个字节的和,并送至SUM变量
DSEG SEGMENTBUFF DB 40H,82H,0F2H,05H,0,10H,0,18HSUM DW ?
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX, DSEGMOV DS, AXXOR AX,AX; 累加器AX清零MOV SI,OFFSET BUFF;用作数据区的指令SI指向数据区的起始偏移地址MOV CX,8;循环次数置为8AGAIN:ADD AL,[SI]ADC AH,0; 高字节加上进位INC SI; LOOP AGAINMOV SUM,AX
CSEG ENDS
ENDS START
LOOPZ
该指令与LOOP指令的区别在于,在判断CX是否为0的同时还要判断ZF的标志。若CX不为0且ZF=1则转至该指令的短标号处; 否则顺序执行LOOPZ指令后的指令。LOOPNZ
该指令与LOOP指令的区别在于,若CX不等于0,且ZF=0则转至该指令的短标号处; 否则顺序执行LOOPNZ指令后的指令。
【例】求BUFF数据区第一个零元素之前的各个字节数之和,并送至SUM变量
DSEG SEGMENTBUFF DB 40H,82H,0F2H,05H,0,10H,0,18HSUM DW ?
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX, DSEGMOV DS, AXXOR AX,AX; 累加器AX清零MOV SI,OFFSET BUFF;用作数据区的指令SI指向数据区的起始偏移地址MOV CX,8;循环次数置为8AGAIN:ADD AL,[SI]ADC AH,0; 高字节加上进位INC SI; CMP BYTE PTR[SI],0; 将下一个字节数与0作比较,比较结果将影响ZF标志LOOPNZ AGAINMOV SUM,AX
CSEG ENDSENDS START
- JCXZ
该指令与LOOP指令的区别有两点: 其一: 没有使CX中的计数值减1的功能; 其二:当(CX)=0时转至该指令的短标号处,否则顺序执行该JCXZ指令后的指令
6.2 串操作指令及重复前缀
串是指存储器中的字节序列或者字序列。串操作指令对字节串或者字串进行每次一个元素的操作。在串操作指令前加上重复前缀,就可以由硬件重复执行该串操作指令,使得处理串的操作速度远远大于重复控制指令处理的速度。串操作指令通常不带操作数,重复前缀只能配合串操作指令使用。
- MOVSB、MOVSW(串传送指令)
功能: 将DS:SI所指的源操作数传送到ES:DI所指的目的位置,指令MOVSB实现字节传送,MOVSW实现字传送。然后修改SI, DI的内容使之指向下一个元素的位置。对SI,DI的修改取决于两个因素:其一是操作数的属性,其二是方向标志DF的状态。具体修改方法如下:
- MOVSB指令: 若DF=0,则SI<-(SI)+1, DI<-(DI)+1; 否则SI<-(SI)-1,DI<-(DI)-1
- MOVSW指令:若DF=0,则SI<-(SI)+2,DI<-(DI)+2; 否则SI<-(SI)-2,DI<-(DI)-2;
注意:
该指令不影响状态标志。
在其前加上重复前缀REP可实现串从一个存储区到另一个存储区的传送。
在使用重复前缀的情况下,应预先将重复的次数送至寄存器CX, 并根据需要使用指令CLD使得DF置“0”,或者使用指令STD使得DF置“1”
【例】要求将BLKS为首址的连续N个字节数传送至BLKD为首址的存储区,且假设这两个存储区不重叠。
DSEG SEGMENTBLKS DBN EQU$-BLKSBLKD DB N DUP(?)
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AX;置源串段地址MOV ES,AX;置目的串段地址MOV CX,N;置串重复操作次数MOV SI,OFFSET BLKS;置源串偏移地址MOV DI,OFFSET BLKD;置目的串偏移地址CLD;DF<- 0, 以使其后每次传送工作之后地址指针做“+”修改REP MOVSB; 重复N次字节传送操作MOV AH,4CHINT 21H;返回DOS
CSEG ENDSENDS START
LODSB、LODSW(串装入指令)
功能:与MOVSB、MOVSW指令的区别仅在于,这两条指令所作数据传送的目的位置分别为AL、AX,不涉及寄存器DI。STOSB、STOSW(串存储指令)
与MOVSB、MOVSW指令的区别仅在于,这两条指令源操作数分别为(AL)、(AX),不涉及寄存器SI。在其前加上重复前缀REP可以使一个存储区各单元置同一数据。
【例】使得BUFF数据区各单元存放用户输入的同一个字符
DSEG SEGMENTBUFF DB 100 DUP(?)
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV ES,AXINT 21H;等待用户输入MOV DI,OFFSET BUFF;DI指向目的串的初始位置MOV CX,100;置串操作的重复次数CLD;使其后的串操作指令对地址指针做“+”修改REP STOSB;将(AL)送BUFF数据区各个字节单元,重复前缀REP的功能是使所缀指令STOSB重复CX初值所指定的次数MOV AH,4CHINT 21H;返回DOS
CSEG ENDSENDS START
CMPSB、CMPSW(串比较指令)
功能:将DS:SI所指的源操作数减去ES:DI 所指的目的操作数,但不送回差值,只是根据减法运算产生的状态标志。这两条指令对SI、DI的修改分别与MOVSB、MOVSW相同。SCASB、SCASW(串搜索指令)
功能:与CMPSB、CMPSW指令的区别仅在于,这两条指令所规定的被减数分别为(AL)、(AX), 不涉及寄存器SI。在其前加上重复前缀REPZ或者REPNZ,可以在ES:DI所指的一个存储区中寻找与(AL)、(AX)不等或者相等的元素。REP前缀
功能:使得所缀的串操作重复CX初值所指定的次数。在使用该前缀及其串操作指令前,应根据需要对CX设置初值。REP前缀常配合MOVSB、MOVSW、STOSB、STOSW指令使用。REPZ前缀
功能:当所缀的串操作指令尚未执行CX初值所指定的次数,且所缀串操作指令的执行使得ZF=1,则重复执行所缀的串操作指令;否则顺序执行串操作指令的下一指令。REPZ前缀常配合CMPSB、CMPSW、SCASB、SCASW指令使用PEPNZ前缀
REPNZ前缀与REPZ前缀的区别仅在于,使所缀的串操作指令重复执行的条件不同。当所缀的串操作指令尚未执行CX初值所指定的次数,且所缀串操作指令的执行使得ZF=0,则重复执行所缀的串操作指令;否则顺序执行串操作指令的下一指令。
【例】将BUFF数据区中紧接着第一个零元素后的字节数送ANS单元(设在末元素前存在零元素)
DSEG SEGMENTBUFF DB 40H,82H,0F2H,05H,0,10H,0,18HANS DB ?
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV ES,AXXOR AL,AL;搜索对象置为0MOV DI,OFFSET BUFF;DI指向搜索区间起始位置MOV CX,8; 最大搜索次数CLD; 使得其后的串操作指令对地址指针做“+”修改REPNZ SCASB;根据0-ES:[DI]影响标志ZF,且DI<-(DI)+1,CX<-(CX)-1。若(CX)不等于0,且ZF,则重复执行SCASB指令MOV AL,ES:[DI]MOV ANS,AL; 将紧接着第一个零元素后的字节数送至ANS单元MOV AH,4CHINT 21H;返回DOS
CSEG ENDSENDS START
INSB、INSW、INSD串输入指令
功能: 将(DX) 端口的1个字节、字、双字送到ES:DI所指的目的位置,且修改DI的内容,使之指向下一目的位置。OUTSB、OUTSW、OUTSD指令
这3条指令的功能分别是将DS: SI(或DS: ESI)所指的1个字节、1个字或1个双字输出到(DX)端口,且修改SI(或ESI)的内容,使之指向下一源位置。修改的方法与前述串指令相同。串输入和串输出指令均不影响状态标志。
6.3 循环程序设计
6.3.1 计数控制的循环程序设计
【例1】编写计算N!的程序
DSEG SEGMENTN EQU 10;任意一个自然数ANS DD ?
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXMOV ECX,NMOV EAX,1; NEXT:IMUL EAX,ECX;LOOP NEXT;MOV ANS,EAX;MOV AH,4CHINT 21H;返回DOS
CSEG ENDSENDS START
【例2】编写一程序,以统计BUF字数据区中负数的个数。
DSEG SEGMENTBUF DW 0,8200H,42H,0FFFFH,1200H,3203HDW 0C000H,9030H,6800H,10H,08H,2222HCOUNT EQU ($-BUF)/2ANS DB?
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXXOR AL,ALMOV BX,OFFSET BUFMOV CX,COUNT
NEXT:CMP WORD PTR[BX],0JGE GEZ;BX所指的有符号双字节不为负数INC AL
GEZ:INC BXINC BX;LOOP NEXT;MOV ANS,ALMOV AH,4CHINT 21H;返回DOS
CSEG ENDSENDS START
6.3.2 条件控制的循环程序设计
【例1】编写一程序,用以判断BUF1和BUF2两个等长度的数据区中数据是否相同。相同则使FLAG单元置0,否则置-1。
DSEG SEGMENTBUF1 DB (N个字节数)BUF2 DB (N个字节数)COUNT EQU $-BUF2FLAG DB 0
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXMOV SI,OFFSET BUF1-1MOV DI,OFFSET BUF2-1MOV CX,COUNT;置循环初值
NEXT:INC SIINC DIMOV AL,[SI]CMP AL,[DI]JZ LOOP1MOV FLAG,-1JMP OK;
LOOP1: LOOP NEXT;
OK:MOV AH,4CHINT 21H;返回DOS
CSEG ENDSENDS START
【例2】使用LOOPZ、LOOPNZ指令实现循环的条件控制。要求实现的程序功能和例1相同
DSEG SEGMENTBUF1 DB (N个字节数)BUF2 DB (N个字节数)COUNT EQU $-BUF2FLAG DB 0
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXMOV SI,OFFSET BUF1-1MOV DI,OFFSET BUF2-1MOV CX,COUNT;置循环初值
NEXT:INC SIINC DIMOV AL,[SI]CMP AL,[DI]LOOPZ NEXT;JZ OKMOV FLAG,-1
OK:MOV AH,4CHINT 21H;返回DOS
CSEG ENDSENDS START
【例】编写一段程序,要求满足1+2+…+x<8000的最大的X的值
DSEG SEGMENTCONS EQU 8000X DW ?
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXXOR AX,AXXOR BX,BX
NEXT:INC BXADD AX,BXCMP AX,CONSJB NEXTDEC BXMOV X,BXMOV AH,4CHINT 21H;返回DOS
CSEG ENDSENDS START
6.3.3 多重循环程序设计
【例1】编写一程序,用以统计BUF数据区的64个字节中“0”二进制位的数目。
DSEG SEGMENTBUF DB (64个字节数)COUNT DW 0
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXMOV SI,OFFSET BUFMOV CX,64;置外层循环初值
EXL:XOR BX,BXMOV DH,8MOV AL,[SI]
INL :ROR AL,1JC NEXTINC BX;
NEXT:DEC DHJNZ INLINC SIADD COUNT,BXLOOP EXL;MOV AH,4CHINT 21H;返回DOS
CSEG ENDSENDS START
6.4 循环程序综合设计举例
【例1】 编写一程序,以实现N个无序的有符号字节数组由大到小的排序
DSEG SEGMENT
X DB 0,34H,90H,0FFH,81H,11H,07H,10H
COUTN EQU$-X
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXMOV DX,COUTN-1; 置外层循环初值
EXL:XOR BL,BLMOV CX,DXMOV SI,OFFSET X; 置内层循环初值
INL:MOV AL,[SI]CMP AL,[SI+1]JGE NEXTXCHG AL,[SI+1]MOV [SI],AL MOV BL,0FFH
NEXT:INC SI;LOOP INL;CMP BL,0JZ OK;DEC DX;JNZ EXL;
OK:MOV AH,4CHINT 21H;返回DOS
CSEG ENDSENDS START
【例2】编写一程序,以实现以下矩阵相乘。
DSEG SEGMENT
A DB 3,0,2,3DB 1,1,2,0DB 1,0,2,3DB 1,0,0,1
B DB 1,0,0,1
C DW 3DUP(?)
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXMOV SI,0MOV BX,0MOV CX,3
LOOP1:PUSH CXMOV DI,0MOV WORD PTR C[BX],0MOV CX 4
LOOP2:MOV AH,0MOV AL,A[SI]MUL B[DI]ADD C[BX],AXINC SIINC DILOOP LOOP2ADD BX,2POP CXLOOP LOOP1MOV AH,4CHINT 21H;返回DOS
CSEG ENDSENDS START
7. 子程序的设计和系统调用
7.1 调用和返回指令
就子程序是否与调用程序在同一段代码而言,调用分为近调用和远调用;就指令是否给出子程序的标号而言,调用可以分为直接调用和间接调用。
指令指针IP(或EIP)总是存放要执行的下一指令的偏移地址,而CS总是存放要执行的下一指令的段地址。大多数指令都遵循这样的规律:在取指令和执行指令后,总是自动使指令指针加上该指令的字节数,使之指向顺序排列的下一指令。
CALL指令将顺序排列的下一指令的地址压入堆栈以便返回,而将该指令给出的转移目标地址送IP(或EIP)如果该CALL指令为远调用指令,CS的内容将是该指令指定位置的段地址
指令将处于栈顶的地址弹至IP(或EIP)以实现返回。对于远调用而言,RET指令还要恢复调用前CS的内容。
7.2 子程序的设计
7.2.1 子程序的定义
- 子程序定义的格式
子程序可供近调用,即段内调用
标号 PROC NEAR; 其中的NEAR可以省略
...
标号 ENDP
子程序可供远调用,即段间调用
标号 PROC FAR
...
标号 ENDP
- 子程序的结构
子程序在遵循上述格式的基础上,通常有如下结构:
标号 PROC NEAR或FAR保护现场根据入口参数进行处理产生出口参数恢复现场RET
标号 ENDP
7.2.2 子程序的调用和返回
【例1】近直接调用:计算Cmn=m!/(n!∗(m−n)!)C_m^n=m!/(n!*(m-n)!)Cmn=m!/(n!∗(m−n)!)的值(m,n 为自然数,且m>n)
DSEG SEGMENTM EQU (一个自然数)N EQU (一个自然数)ANS DD ?
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT USE 16;16位段
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXMOV ECX,NCALL SUB1;MOV EBX,EAX;MOV ECX,MCALL SUB1;DIV EBX;MOV EBX,EAX;MOV ECX,MSUB ECX,NCALL SUB1;XCHG EBX,EAXDIV EBX;MOV ANS,EAXMOV AH,4CHINT 21H;返回DOSSUB1 PROC MOV EAX,1NEXT:MUL EAX,ECXLOOP NEXTRETSUB1 ENDPCSEG ENDS
ENDS START
以上程序中,调用程序和子程序处于同一个代码段,CALL指令通过标号给出转子的目标位置SUB1,采用的是近直接调用。例如,在执行第一条CALL指令时,将当前的IP内容,即将其下一指令的偏移地址压入堆栈,然后将标号SUB1的偏移地址送IP,从而使程序转向SUB1子程序。当子程序执行到RET指令时,将先前压入堆栈的CALL指令下一指令的偏移地址弹至IP,从而使程序返回到调用指令的下一指令。
【例2】以下程序功能与例1程序相同,但调用程序与子程序不处于同一代码段,采用的是远直接调用。
程序采用的是远调用,故调用时既要将当前的IP内容,又要将当前的CS内容入栈,而子程序返回时将入栈的内容弹回这两个寄存器。例如,在执行第一条CALL指令时,将当前的CS内容,即将CSEG段地址压入椎栈,将当前IP内容,即将其下一指令的偏移地址压入堆栈,然后将标号SUB1所在段的段值,即SUBC段的段地址送CS,将标号SUB1的偏移地址送IP,从而使程序转向SUB1子程序。当子程序执行到RET指令时,将栈顶内容分别弹至IP和CS,从而使程序返回到调用指令的下一指令
【例3】近间接调用的实例
DSEG SEGMENTTABW DW SUB0,SUB1,SUB2,SUB3,SUB4,SUB5,SUB6,SUB7,SUB8,SUB9
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT USE 16;16位段
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXMOV BX,OFFSET TABW;MOV AH,1INT 21H;等待输入一个数字字符0~9并送至ALXOR AH,AHAND AL,OFH;将AL中的数字字符转换为对应的数字,并送往AXADD AX,AXADD BX,AX;BX 指向第i个子程序入口CALL WORD PTR[BX]; 近直接调用MOV AH,4CHINT 21H;返回DOSSUB0 PROC ...SUB0 ENDP...SUB9 PROC ...SUB9 ENDPCSEG ENDS
ENDS START
7.2.3 保护现场和恢复现场
(1)在调用程序中保护现场与恢复现场。具体而言,就是在CALL指令前保护现场,而在CALL指令后恢复现场
【例1】设在所调用的子程序SUB1中有可能改变寄存器BX、CX及标志寄存器FLAGS的内容,而调用程序要求在子程序返回后,能够在BX、CX及FLAGS原有内容的基础上继续执行其后的程序段,可以使用以下方法: 各个寄存器内容入栈的次序应当与将其出栈的次序相反
PUSH BX
PUSH CX
PUSHF
CALL SUB1
POPF
POP CX
POP BX
(2)在子程序中保护现场与恢复现场。具体而言,就是在子程序起始处保护现场,而在其返回指令,即RET指令前恢复现场。这种方法较为常用。
【例2】以下方法也能达到例1所提出的要求。
SUB1 PROC PUSH BXPUSH CXPUSHF...POPFPOP CXPOP BXRET
SUB1 ENDP
(3) 保护现场与恢复现场的简便方法
在80286、80386及其以上指令集中具有将所有通用寄存器入栈及出栈的专用指令。如需要在子程序SUB1中保护与恢复AX、CX、DX、BX、SP、BP、SI、DI及FLAGS的内容,则可以用以下方法:
SUB1 PROC PUSHAPUSHF...POPFPOPARET
SUB1 ENDP
需要在子程序SUB1中保护与恢复EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI及EFLAGS的内容,则可以用以下方法:
SUB1 PROC PUSHADPUSHFD...POPFDPOPADRET
SUB1 ENDP
7.2.4 参数的传递
常用的参数传递方法有约定寄存器法、约定存储单元法及堆栈法。
- 约定寄存器法: 约定寄存器法是指事先约定使用某些寄存器进行入口参数、出口参数的传递
【例1】编写程序,用以统计字节数组中零元素的个数
分析: 对于一个首地址为ARRAYB,字节数为COUNT的字节数组来说,应将ARRAYB及COUNT作为入口参数提供给子程序;而子程序应在统计出ARRAYB开始的COUNT个字节数中零元素个数后,将零元素个数作为出口参数提供给调用程序。调用程序最后将出口参数送存储单元
在编写程序和子程序前,约定使用寄存器来实现入口参数和出口参数的传递。考虑到子程序需要对ARRAYB开始的各个字节数判断其是否为零元素,将ARRAYB作为SI的初值,且逐次使SI增“1”,则可用[SI]表示各个字节数。于是可以约定,使用寄存器SI传递入口参数ARRAYB。考虑到子程序需要重复判断[SI]是否为零元素,重复次数为COUNT,而可实现控制重复的LOOP指令使用寄存器CX实现重复计数,于是可以约定,使用寄存器CX传递入口参数COUNT。
DSEG SEGMENTARRAYB DB (若干个字节数)COUNT EQU $-ARRAYBANS DW ?
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT USE 16;16位段
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXLEA SI,ARRAYBMOV CX,COUNT ;约定寄存器SI、CX为子程序提供的入口参数CALL ZNUMMOV ANS,AXMOV AH,4CHINT 21H;返回DOSZNUM PROC XOR AX,AXNEXT:CMP BYTE PTR[SI],0JNZ NZINC AXNZ:INC SILOOP NEXT RET ZNUM ENDP
CSEG ENDS
ENDS START
- 约定存储单元法
约定存储单元法是指事先约定使用某些存储单元进行入口参数、出口参数的传递。
【例2】程序功能与【例1】同,但要求使用约定存储单元法实现参数的传递。
分析: 改用PARA1、PARA2单元传递入口参数,改用PARA3传递出口参数
DSEG SEGMENTARRAYB DB (若干个字节数)COUNT EQU $-ARRAYBANS DW ?PARA1 DW ?PARA2 DW ?PARA3 DW ?
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT USE 16;16位段
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXLEA SI,ARRAYBMOV PARA1,SIMOV PARA2,COUNT;通过约定的寄存单元PARA1和PARA2为子程序提供入口参数CALL ZNUMMOV AX,PARA3;通过约定的寄存器单元PARA3接受子程序提供的出口参数MOV ANS,AXMOV AH,4CHINT 21H;返回DOSZNUM PROC MOV SI,PARA1MOV CX,PARA2;从约定的存储单元PARA1、PARA2中取入口参数XOR AX,AXNEXT:CMP BYTE PTR[SI],0JNZ NZINC AXNZ:INC SILOOP NEXT MOV PARA3,AXRET ZNUM ENDP
CSEG ENDS
ENDS START
- 堆栈法
堆栈法是指通过堆栈进行入口参数、出口参数的传递。
【例3】编写程序,程序功能与【例1】同,但要求使用堆栈法实现参数的传递
分析:以下程序中,在调用程序的CALL指令前将入口参数压入堆栈,在子程序起始处将入口参数弹出堆栈以便子程序使用,即实现入口参数的传递;在子程序的RET指令前将出口参数压入堆栈,在调用程序的CALL指令后将出口参数弹出堆栈以便调用程序使用
DSEG SEGMENTARRAYB DB (若干个字节数)COUNT EQU $-ARRAYBANS DW ?
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT USE 16;16位段
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXLEA SI,ARRAYBMOV CX, COUNTPUSH SIPUSH CX;通过堆栈接受子程序提供的出口参数CALL ZNUMPOP AX;通过堆栈接受出口参数MOV ANS,AXMOV AH,4CHINT 21H;返回DOSZNUM PROC MOV BP,SPMOV CX,[BP+2]MOV SI,[BP+4]XOR AX,AXNEXT:CMP BYTE PTR[SI],0JNZ NZINC AXNZ:INC SILOOP NEXT MOV [BP+4],AXRET 2ZNUM ENDP
CSEG ENDS
ENDS START
7.3程序的嵌套和递归
7.3.1 子程序的嵌套
子程序可以调用另一个子程序,这种调用结构称为子程序的嵌套
【例1】 设BUF数据区有两个双字节无符号数,试求两者之和并将其存入SUM单元。然后将SUM单元的内容以十六进制形式在屏幕上显示出来。
分析:子程序SUB0为子程序SUB1提供数据区首址并调用SUB1;子程序SUB1实现两个双字节数的相加运算并存储结果,为子程序SUB2提供要显示的数据,并且调用SUB2;子程序SUB2实现数据的显示。
DSEG SEGMENTBUF DW (两个双字节数)SUM DW ?
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEGSUB0 PROC FARPUSH DSMOV AX,0PUSH AX;为返回DOS做准备MOV AX,DSEGMOV DS,AXLEA SI,BUF;通过约定的寄存器SI为子程序SUB1提供入口参数CALL SUB1RET; 返回DOSSUB0 ENDP
SUB1 PROC MOV AX,[SI]ADD AX,[SI+2]MOV SUM,AX;存储结果,同时通过与欸的那个的存储单元SUM为子程序SUB2提供入口参数CALL SUB2RET;返回SUB0SUB1 ENDP
SUB2 PROC PUSH BXPUSH CXPUSH AXPUSH DXMOV BX,SUMPOP DXMOV CH,4POP AXMOV CL,4POP CX
NEXT:ROL BX,CLPOP BXMOV AL,BLAND AL,0FHRET;返回SUB1ADD AL,30HSUB2 ENDPCMP AL,3AHJB BACSEG ENDS
END SUB0
ADD AL,07H
BA: MOV DL,ALMOV AH,02HINT 21H;显示一位16进制数DEC CHJNZ NEXT
7.3.2 子程序的递归
子程序调用本身,或通过调用另一个子程序调用本身的结构称为子程序的递归。前者称为直接递归,后者称为间接递归
以下子程序采用了直接递归:
SUB1 PROC ...CALL SUB1...RET
SUB1 ENDP
以下子程序采用了间接递归:
SUB1 PROC ...CALL SUB2...
SUB1 ENDPSUB2 PROC ...CALL SUB1...RET
SUB2 ENDP
【例1】编写计算N!的程序。假设。
N≤8,即N!不超出两字节无符号数的范围
分析: 可以在递归子程序中通过自身调用将N,N-1,…,1依次压入堆栈。这里是否已将1压入堆栈就是递归结束条件。一旦递归结束条件满足,则依次将堆栈中的1,2,…,N出栈相乘,从而算出N!
递归子程序如下:
FACT PROC PUSH DXMOV DX,AXCMP AX,0; 判断递归结束的条件JZ FRDEC AXCALL FACT; 计算(N-1)!MUL DX;计算N!POP DX;RET
FR:MOV AX,1POP DXRET
FACT ENDP;
7.4 子程序调用和系统功能调用
7.4.1 系统功能调用的方法
系统功能调用分为DOS功能调用和BIOS功能调用。与用户子程序调用不同,系统功能调用是使用INT n指令,n称为类型号。
DOS功能调用固定使用INT 21H指令。当然,在该指令前要置入口参数,并将功能号送寄存器AH。
注意:
(1)近调用与远调用对堆栈的影响不同。执行近调用指令时,当前的(IP)压入堆栈,而执行远调用指令时,先后入栈的内容分别为(CS)及(IP)。这一点在采用堆栈法进行参数传递时尤其值得注意。
(2)要搞清保护、恢复现场与参数传递之间的关系。对于在子程序执行过程中内容可能发生变化的寄存器或存储单元,该不该在子程序起始处加以保护,在子程序的RET指令前予以恢复?这要视具体问题而定。对于一个具体问题而言,若调用程序要求在调用子程序后这些寄存器或存储单元内容不变,就应该将它们作为现场进行保护和恢复;若调用程序正是需要使用调用子程序后这些寄存器或存储单元变化后的内容,则它们的作用是进行出口参数的传递,在这种情况下将它们作为现场进行保护和恢复就达不到预期目的
7.4.2 子程序设计综合实例
【例】输入一串字符,分别统计其中字母、数字及其他字符的个数,并在显示器上输出。
分析:
(1)输入一串字符。可以使用特殊的子程序调用——DOS系统功能调用实现,具体是采用1号DOS系统功能调用。由于字符个数未知,可以实施循环调用,并以Enter符作为结束标志。
(2)统计其中字母、数字及其他字符的个数。可以在字符输入后即做出判断,并用3个寄存器分别存放这3类字符的个数。这里假设输入时“CapeLock”处于打开状态,即输入的字母为大写形式。
(3)显示这串字符中字母、数字及其他字符的个数。可通过3次调用显示子程序实现。在显示子程序中,某类字符个数的显示又可以使用特殊的子程序调用——DOS系统功能调用实现,具体是采用2号DOS系统功能调用。
DSEG SEGMENT
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: XOR BL,BL; BL用于统计数字字符的个数XOR CH,CH; CH用于统计字母字符的个数XOR CL,CL; CL用于统计其他字符的个数
AGAIN:MOV AH,1INT 21HCMP AL,0DHJZ EXITCMP AL,'0'JB OTHERCMP AL,'9'JA NEXTINC BLJMP AGAIN
NEXT:CMP AL,'A'JB OTHERCMP AL,'Z'JA OTHER INC CHJMP AGAIN
OTHER:INC CLJMP AGAIN
EXIT:MOV DL,BLCALL DISPMOV DL,CLCALL DISP
DISP PROC ADD DL,30HMOV AH,2INT 21HRET
DISP ENDP
CSEG ENDSEND STRAT
【例2】
输入某班一门课程的考试成绩,并存放到ASM存储区,将最高分存放到变量MAX,并在显示器上输出。设该班人数为30,考试成绩不超过99分
分析:
(1)输入30个两位数。每个两位数的输入可以分别通过一键盘输入子程序实现,在键盘输入子程序中,可通过7号DOS系统功能调用实现。
(2)将输入的两位数由ASCII码转换为压缩的BCD码,可通过ASCII到BCD转换子程序实现。
(3)产生最高分。可通过求最高得分子程序实现。
(4)显示最高分。首先需要将压缩的BCD码形式的最高分转换成两个ASCII码,这可以通过BCD到ASCII转换子程序实现。然后通过一显示子程序实现,在显示子程序中,可以通过2号DOS系统功能调用实现。
DSEG SEGMENT
ASM DB 30 DUP(?)
MAX DB ?
DSEG ENDSSSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDSCSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START: MOV AX,DSEGMOV DS,AXLEA DI,ASMMOV CH,30
AGAIN:CALL KEYIN; 调用键盘输入子程序,以输入十位数MOV BL,ALCALL KEYIN; 调用键盘输入子程序,以输入个位数CALL A_B; 调用ASCII->BCD转换子程序生成压缩的BCD码存储MOV [DI],AL ; 压缩的BCD码存入ASM的存储区INC DIDEC CH JNZ AGAINLEA DI,ASMMOV CX,30CALL CMAX; 调用求最高得分的子程序CALL B_A;调用BCD->ASCII转换子程序CALL DISP;MOV AH,4CHINT 21H
KEYIN PROC MOV AH,7
B_A PROC INT 21HMOV AL,MAXRET MOV BL,AL
KEYIN ENDPAND AL,0F0HMOV CL,4SHR AL,CL
A_B PROC ADD AL,30HMOV CL,4AND BL,0FHSHL BL,CLADD BL,30HSUB AL,30HRET ADD AL,BLB_A ENDPRET
A_B ENDPDISP PROC MOV DL,AL
CMAX PROC MOV AH ,2DEC CXINT 21HMOV AL,[SI]MOV DL,BL
L1: CMP AL,[SI+1]MOV AH,2JA NEXT INT 21HMOV AL,[SI+1]RET
NEXT:INC SI
DISP ENDPLOOP L1MOV MAX,AL
CSEG ENDSRET END STRATCMAX ENDP
参考
《汇编语言程序设计》丁辉主编
16位汇编语言学习笔记(2)—— 汇编程序设计相关推荐
- 16位汇编语言学习笔记(1)——基础知识
文章目录 1.配置汇编学习环境 1.1 工具下载 1.2 配置环境 2. 汇编命令基础 2.1 简单使用 2.2 常用命令 3. 汇编语言基础 3.1 汇编语言程序与汇编程序 3.2 汇编语言程序的格 ...
- windows下32位汇编语言学习笔记
windows下32位汇编语言学习笔记 第一章 第一章 背景知识 80x86处理器的存储器 4个数据寄存器 EAX,EBX,ECX,EDX EAX寄存器 所有API函数的返回值都保存在EAX里,注意 ...
- 32位汇编语言学习笔记(45)--测试简单文件操作接口(完)
这是<Assembly Language step by step programming with linux>书中的最后一个程序,也是全书中的最复杂的一个程序. 首先看一下这个程 ...
- 王爽 16 位汇编语言学习记录
以下为汇编学习记录,内容全部出自王爽的16位<汇编语言>,如有错误,可直接去查看原书. 汇编语言 机器语言是机器指令集的集合,机器指令是一列二进制数字,计算机将其翻译成高低电平,从而使 ...
- 32位汇编语言学习笔记(43)-- 生成随机数
此程序出自<Assembly Language step by step programming with linux>第12章,用于演示随机数函数的使用,共涉及两个随机数函数: v ...
- 32位汇编语言学习笔记(33)--aaa指令
aaa(ASCII adjust after addition)指令,是BCD指令集中的一个指令,用于在两个未打包的BCD值相加后,调整al和ah寄存器的内容. BCD(Binary-coded ...
- 32位汇编语言学习笔记(36)--repne scasb指令
repne scasb指令,用于扫描字符串,计算字符串的长度,如下两条指令: cld repne scasb 对应的等价指令是: scans:inc edi dec ecx je ...
- win32汇编语言学习笔记(三)
汇编语言学习笔记(三) CH3.Windows汇编基础 .386 .model flat,stdcall option casemap:none 定义程序使用的指令集.工作模式 相应的还有:.8086 ...
- 16位汇编语言第二讲系统调用原理,以及各个寄存器详解
16位汇编语言第二讲系统调用原理,以及各个寄存器详解 昨天已将简单的写了一下汇编代码,并且执行了第一个显示到屏幕的helloworld 问题? helloworld怎么显示出来了. 一丶显卡,显存的概 ...
最新文章
- 华硕路由器安装aria2_网易UU加速器联动华硕,瞄准主机玩家联网刚需
- python ndarray
- 智能人脸识别行业解决方案
- 机器学习中的常见问题—损失函数
- 途牛自营门市超500家 单笔订单交易额最高近300万元
- linux常用命令,知识在于总结
- android 实现仿QQ登录可编辑下拉菜单
- 学习总结:机器学习(一)
- 【转】Magento 2数据库EAV模型结构
- mysql进阶知识_Mysql面试知识点总结(进阶篇)
- scala 判断字段 是不是 日期类型_scala 使用指南,降低新手入门难度
- 阅读verilog程序总结
- PowerBI开发 第四篇:DAX表达式
- verilog实现多周期处理器之——(三)数据相关问题及其解决
- 有什么软件测试固态硬盘,多个专业软件评测中端固态硬盘
- interface和abstract interface
- 面试|详细分析ScheduledThreadPoolExecutor(周期性线程池)的原理
- 安卓APP源码和设计报告——智能垃圾桶
- IBM ILOG CPLEX Optimization Studio V12.9.0官方文档
- QComboBox自定义(一)--类似QQ登陆界面的下拉框