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 SEGMENT
MESS1 DB'Press any key when you are ready', OAH,ODH,'$'
MESS2 DB'Input your password:' , OAH,ODH,'$'
DSEG ENDS
SSEG SEGMENT
STACK DB 80H DUP(0)
SSEG ENDS
CSEG SEGMENT
ASSUME DS:DSEG,SS:SSEG,CS,CSEG
START:
MOV AX, DSEG
MOV DS,AX
MOV DX,OFFESET,MESS1
MOV AH,09H
INT 21H
MOV AH,08H
INT 21H
MOV DX,OFFSET MESS2
MOV AH,09H
INT 21H
CSEG ENDS
END START
【例】求 S = a ∗ b − c 2 S=a*b-c^2 S=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 STACK
DB 80H DUP(0)
SSEG ENDS;
CSEG SEGMENT
ASSUME DS:DSEG,SS:SSEG,CS:CSEG
START: MOV AX,DSEG
MOV DS,AX
MOV AL,C
MUL AL
MOV DX,AX
MOV AL,A
MUL B
SUB AX,DX
MOV S,AX
MOV AH,4CH
INT 21H
CSEG ENDS
END START
【例】根据键盘输入的0~9中任一数字,计算出该数字的平方值,并以非压缩BCD码的形式存放于AX。AH存放十位数,而AL存放个位数。
DSEG SEGMENT
DSEG ENDS;
SSEG SEGMENT
STACK DB 80H DUP(0)
SSEG ENDS;
CSEG SEGMENT
ASSUME DS:DSEG,SS:SSEG,CS:CSEG
START:
MOV AH,1
INT 21H; AL<- 输入的数字字符,如8
AND AL,0FH
MUL AL; AX<-8*8 即40H
AAM; AX<-0604H
MOV AH,4CH
INT 21H
CSEG ENDS
END 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 SEGMENT
ASSUME DS:DSEG,SS:SSEG,CS:CSEG
START:
MOV AX,DSEG
MOV DS,AX
MOV AL,X
CMP AL,Y; 根据X-Y产生状态标志
JL XL; 有符号数X<Y 则转XL
SUB AL,Y
MOV Z, AL; Z<- X-Y
XL:
MOV BL,Y
SUB BL,AL
MOV Z,BL;Z<- Y-X
OK:
MOV AH,4CH
INT 21H; 返回DOS
CSEG ENDS
END 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 SEGMENT
ASSUME DS:DSEG,SS:SSEG,CS:CSEG
START:
MOV AX,DSEG
MOV DS, AX
MOV AL,X
SUB AL,Y; AL<- X-Y 且产生状态标志
JNS XG; 差值为正则转XG处
NEG AL
XG: MOV Z,AL
OK: MOV AH,4CH
INT 21H; 返回DOS
CSEG ENDS
END START
【例2】根据键盘输入的数字0,1,2或3分别显示信息“8086/8088”,“80386”,“80486”或“Pentium”
DSEG SEGMENT
MESS0 DB '808/8080','$'
MESS1 DB '80386','$'
MESS2 DB '80486','$'
MESS3 DB 'Pentium','$'
DSEG ENDS;
SSEG SEGMENT STACK
DB 80H DUP(0)
SSEG ENDS;
CSEG SEGMENT
ASSUME DS:DSEG,SS:SSEG,CS:CSEG
START:
MOV AX,DSEG
MOV DS,AX
MOV AH,8
INT 21H; 从键盘输入一个字符,其ASCII码送AL
AND AL,0FH; 将AL中的'0','1','2','3'转换成对应的数字0,1,2,3
CMP AL,0
JZ M0; AL=0 则转M0处
CMP AL,1
JZ M1;AL=1 则转M1处
CMP AL,2
JZ M2; AL=2 则转M2处
M3:
MOV DX,OFFSET MESS3
JMP SHORT DISP
M0:
MOV DX,OFFSET MESS0
JMP SHORT DISP
M1:
MOV DX,OFFSET MESS1
JMP SHORT DISP
M2:
MOV DX,OFFSET MESS2
DISP:
MOV AH,09H
INT 21H; 显示DX所指的字符串
MOV AH,4CH
INT 21H; 返回DOS
CSEG ENDS
END START
5.2.2 跳转表法分支程序设计
跳转表是一段连续的存储区,根据其内容可以分为分支地址表和转移指令表两种,由此对应两种跳转表法分支程序设计。
设计思想: 通过DW指令将n个分支入口处的偏移地址如
B
R
i
BR_i
BRi 按顺序存放在一段连续的存储区中,构成跳转表。设该区的首地址为BASE, 第i个分支入口的偏移地址的存放位置为BASE +2i 。当程序需要转向第i个分支时,只需将2i送BX,并使用指令
J
M
P
B
A
S
E
[
B
X
]
JMP BASE[BX]
JMPBASE[BX]
【例】 根据键盘输入的数字0,1,2或3,分别显示信息“8086/8088”,“80386”,“80486”或“Pentium”。要求配合使用分支地址构成的跳转表实现多分支程序设计。
此题要额外注意,键盘中输入的数字是以ASCII码的形式存储。
DSEG SEGMENT
BASE ;多分支入口处偏移地址构成跳转表
DW M0
DW M2
DW M3
MESSO DB '8086/8088','$'
MESS1 DB '80386','$'
MESS2 DB '80486','$'
MESS3 DB 'Pentium','$'
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
MOV AH,8
INT 21H;从键盘输入0,1,2,3 其ASCII码送入AL
AND AL,0FH; 将输入的0,1,2,3 转换成对应的数字i
SHL AL,1; AL<-2*i
CBW
MOV BX,AX;BX<-2*i
JMP BASE[BX]; 利用地址表实现多分支
M0:
MOV DX,OFFSET MESS0
JMP SHORT DISP
M1:
MOV DX,OFFSET MESS1
JMP SHORT DISP
M2:
MOV DX,OFFSET MESS2
JMP SHORT DISP
M3:
MOV DX,OFFSET MESS3;
DISP:
MOV AH,09H
INT 21H; 显示DX所指的字符串
CSEG ENDS
ENDS START
5.3 分支程序综合举例
【例1】将BLKS为首址的连续N个字节数传送至以BLKD为首址的存储区,编写此数据块传送程序。
分析:
① 两数据块不重叠,在这种情况下从首部或从尾部开始传送均可以。② 两数据块有部分重叠,且BLKS地址大于BLKD地址。在这种情况下,只能从首部开始传送。若从尾部开始传送,则将破坏BLKS数据块中尚未传送的首部数据。
③ 两数据块有部分重叠,且BLKS地址小于BLKD地址。这种情况下,只能从尾部开始传送。
DSEG SEGMENT
ORG $+24
STRG DB THIS IS A PROGRAM FOR STRING MOVING'
N EQU $-STRG
BLKS DW STRG
BLKD DW STRG+5
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX, DSEG
MOV DX,AX
MOV CX,N; CX<-数据块字节数
MOV SI, BLKS; SI 指向源数据块首部
MOV DI,BLKD;DI 指向目的数据块首部
MOV BX,1;设置SI,DI修正量为1
CMP SI,DI
JA MOVE ;源数据地址大于目的地址则转至MOVE处
ADD SI,CX
DEC SI; SI 指向源数据块尾部
ADD DI,CX; DI 指向目的数据块尾部
DEC DI
NEG BX; 设置SI,DI 的修正量为-1;
MOVE:
MOV AL,[SI]
MOV [DI],AL
ADD SI,BX
ADD DI,BX
DEC CX
JNZ MOVE;CX 不等于0,传送尚未完毕,转至MOVE处继续传送
MOV AH,4CH
INT 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 SEGMENT
A DW ?
B DW ?
TAB DW CASE0, CASE12,CASE12,CASE3
DW CASE4, CASE567, CASE567,CASE567
MSG DB 'function_5_6_7$'
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX, DSEG
MOV DS, AX
MOV AX, A
MOV BX, AX
AND BX, 7;得到BX的低3位,实现a%8的计算
SHL BX,1
JMP TAB[BX];利用地址表实现多分支
CASE0:
MOV B,32D
JMP NEXT
CASE12:
ADD AX,43D
MOV B,AX
JMP NEXT
CASE3:
SHL AX,1
MOV B,AX
JMP NEXT
CASE4:
DEC B
JMP NEXT
CASE567:
LEA DX,MSG
MOV AH,9
INT 21H
NEXT:
MOV AH,4CH
INT 21H;返回DOS
CSEG ENDS
ENDS START
6. 循环程序设计
6.1 循环指令
重复控制指令的功能:
将寄存器CX默认为重复控制计数器,根据CX是否为0等情况来控制是转向段标号还是顺序执行。
重复控制指令的一般格式:xxx 短标号
- LOOP
功能:该指令首先使寄存器CX中的计数值减1,然后判断CX是否为0。若CX不等于0,则重复执行短标号开始的程序段,否则顺序执行Loop指令后的指令。
【例】求BUFF数据区中各个字节的和,并送至SUM变量
DSEG SEGMENT
BUFF DB 40H,82H,0F2H,05H,0,10H,0,18H
SUM DW ?
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX, DSEG
MOV DS, AX
XOR AX,AX; 累加器AX清零
MOV SI,OFFSET BUFF;用作数据区的指令SI指向数据区的起始偏移地址
MOV CX,8;循环次数置为8
AGAIN:
ADD AL,[SI]
ADC AH,0; 高字节加上进位
INC SI;
LOOP AGAIN
MOV 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 SEGMENT
BUFF DB 40H,82H,0F2H,05H,0,10H,0,18H
SUM DW ?
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX, DSEG
MOV DS, AX
XOR AX,AX; 累加器AX清零
MOV SI,OFFSET BUFF;用作数据区的指令SI指向数据区的起始偏移地址
MOV CX,8;循环次数置为8
AGAIN:
ADD AL,[SI]
ADC AH,0; 高字节加上进位
INC SI;
CMP BYTE PTR[SI],0; 将下一个字节数与0作比较,比较结果将影响ZF标志
LOOPNZ AGAIN
MOV SUM,AX
CSEG ENDS
ENDS 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 SEGMENT
BLKS DB
N EQU$-BLKS
BLKD DB N DUP(?)
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX;置源串段地址
MOV ES,AX;置目的串段地址
MOV CX,N;置串重复操作次数
MOV SI,OFFSET BLKS;置源串偏移地址
MOV DI,OFFSET BLKD;置目的串偏移地址
CLD;DF<- 0, 以使其后每次传送工作之后地址指针做“+”修改
REP MOVSB; 重复N次字节传送操作
MOV AH,4CH
INT 21H;返回DOS
CSEG ENDS
ENDS START
-
LODSB、LODSW(串装入指令)
功能:与MOVSB、MOVSW指令的区别仅在于,这两条指令所作数据传送的目的位置分别为AL、AX,不涉及寄存器DI。 -
STOSB、STOSW(串存储指令)
与MOVSB、MOVSW指令的区别仅在于,这两条指令源操作数分别为(AL)、(AX),不涉及寄存器SI。在其前加上重复前缀REP可以使一个存储区各单元置同一数据。
【例】使得BUFF数据区各单元存放用户输入的同一个字符
DSEG SEGMENT
BUFF DB 100 DUP(?)
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV ES,AX
INT 21H;等待用户输入
MOV DI,OFFSET BUFF;DI指向目的串的初始位置
MOV CX,100;置串操作的重复次数
CLD;使其后的串操作指令对地址指针做“+”修改
REP STOSB;将(AL)送BUFF数据区各个字节单元,重复前缀REP的功能是使所缀指令STOSB重复CX初值所指定的次数
MOV AH,4CH
INT 21H;返回DOS
CSEG ENDS
ENDS 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 SEGMENT
BUFF DB 40H,82H,0F2H,05H,0,10H,0,18H
ANS DB ?
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV ES,AX
XOR AL,AL;搜索对象置为0
MOV 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,4CH
INT 21H;返回DOS
CSEG ENDS
ENDS 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 SEGMENT
N EQU 10;任意一个自然数
ANS DD ?
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
MOV ECX,N
MOV EAX,1;
NEXT:
IMUL EAX,ECX;
LOOP NEXT;
MOV ANS,EAX;
MOV AH,4CH
INT 21H;返回DOS
CSEG ENDS
ENDS START
【例2】编写一程序,以统计BUF字数据区中负数的个数。
DSEG SEGMENT
BUF DW 0,8200H,42H,0FFFFH,1200H,3203H
DW 0C000H,9030H,6800H,10H,08H,2222H
COUNT EQU ($-BUF)/2
ANS DB?
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
XOR AL,AL
MOV BX,OFFSET BUF
MOV CX,COUNT
NEXT:
CMP WORD PTR[BX],0
JGE GEZ;BX所指的有符号双字节不为负数
INC AL
GEZ:
INC BX
INC BX;
LOOP NEXT;
MOV ANS,AL
MOV AH,4CH
INT 21H;返回DOS
CSEG ENDS
ENDS START
6.3.2 条件控制的循环程序设计
【例1】编写一程序,用以判断BUF1和BUF2两个等长度的数据区中数据是否相同。相同则使FLAG单元置0,否则置-1。
DSEG SEGMENT
BUF1 DB (N个字节数)
BUF2 DB (N个字节数)
COUNT EQU $-BUF2
FLAG DB 0
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
MOV SI,OFFSET BUF1-1
MOV DI,OFFSET BUF2-1
MOV CX,COUNT;置循环初值
NEXT:
INC SI
INC DI
MOV AL,[SI]
CMP AL,[DI]
JZ LOOP1
MOV FLAG,-1
JMP OK;
LOOP1:
LOOP NEXT;
OK:
MOV AH,4CH
INT 21H;返回DOS
CSEG ENDS
ENDS START
【例2】使用LOOPZ、LOOPNZ指令实现循环的条件控制。要求实现的程序功能和例1相同
DSEG SEGMENT
BUF1 DB (N个字节数)
BUF2 DB (N个字节数)
COUNT EQU $-BUF2
FLAG DB 0
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
MOV SI,OFFSET BUF1-1
MOV DI,OFFSET BUF2-1
MOV CX,COUNT;置循环初值
NEXT:
INC SI
INC DI
MOV AL,[SI]
CMP AL,[DI]
LOOPZ NEXT;
JZ OK
MOV FLAG,-1
OK:
MOV AH,4CH
INT 21H;返回DOS
CSEG ENDS
ENDS START
【例】编写一段程序,要求满足1+2+…+x<8000的最大的X的值
DSEG SEGMENT
CONS EQU 8000
X DW ?
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
XOR AX,AX
XOR BX,BX
NEXT:
INC BX
ADD AX,BX
CMP AX,CONS
JB NEXT
DEC BX
MOV X,BX
MOV AH,4CH
INT 21H;返回DOS
CSEG ENDS
ENDS START
6.3.3 多重循环程序设计
【例1】编写一程序,用以统计BUF数据区的64个字节中“0”二进制位的数目。
DSEG SEGMENT
BUF DB (64个字节数)
COUNT DW 0
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
MOV SI,OFFSET BUF
MOV CX,64;置外层循环初值
EXL:
XOR BX,BX
MOV DH,8
MOV AL,[SI]
INL :
ROR AL,1
JC NEXT
INC BX;
NEXT:
DEC DH
JNZ INL
INC SI
ADD COUNT,BX
LOOP EXL;
MOV AH,4CH
INT 21H;返回DOS
CSEG ENDS
ENDS START
6.4 循环程序综合设计举例
【例1】 编写一程序,以实现N个无序的有符号字节数组由大到小的排序
DSEG SEGMENT
X DB 0,34H,90H,0FFH,81H,11H,07H,10H
COUTN EQU$-X
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
MOV DX,COUTN-1; 置外层循环初值
EXL:
XOR BL,BL
MOV CX,DX
MOV SI,OFFSET X; 置内层循环初值
INL:
MOV AL,[SI]
CMP AL,[SI+1]
JGE NEXT
XCHG AL,[SI+1]
MOV [SI],AL
MOV BL,0FFH
NEXT:
INC SI;
LOOP INL;
CMP BL,0
JZ OK;
DEC DX;
JNZ EXL;
OK:
MOV AH,4CH
INT 21H;返回DOS
CSEG ENDS
ENDS START
【例2】编写一程序,以实现以下矩阵相乘。
DSEG SEGMENT
A DB 3,0,2,3
DB 1,1,2,0
DB 1,0,2,3
DB 1,0,0,1
B DB 1,0,0,1
C DW 3DUP(?)
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
MOV SI,0
MOV BX,0
MOV CX,3
LOOP1:
PUSH CX
MOV DI,0
MOV WORD PTR C[BX],0
MOV CX 4
LOOP2:
MOV AH,0
MOV AL,A[SI]
MUL B[DI]
ADD C[BX],AX
INC SI
INC DI
LOOP LOOP2
ADD BX,2
POP CX
LOOP LOOP1
MOV AH,4CH
INT 21H;返回DOS
CSEG ENDS
ENDS 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】近直接调用:计算
C
m
n
=
m
!
/
(
n
!
∗
(
m
−
n
)
!
)
C_m^n=m!/(n!*(m-n)!)
Cmn=m!/(n!∗(m−n)!)的值(m,n 为自然数,且m>n)
DSEG SEGMENT
M EQU (一个自然数)
N EQU (一个自然数)
ANS DD ?
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT USE 16;16位段
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
MOV ECX,N
CALL SUB1;
MOV EBX,EAX;
MOV ECX,M
CALL SUB1;
DIV EBX;
MOV EBX,EAX;
MOV ECX,M
SUB ECX,N
CALL SUB1;
XCHG EBX,EAX
DIV EBX;
MOV ANS,EAX
MOV AH,4CH
INT 21H;返回DOS
SUB1 PROC
MOV EAX,1
NEXT:
MUL EAX,ECX
LOOP NEXT
RET
SUB1 ENDP
CSEG 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 SEGMENT
TABW DW SUB0,SUB1,SUB2,SUB3,SUB4,SUB5,SUB6,SUB7,SUB8,SUB9
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT USE 16;16位段
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
MOV BX,OFFSET TABW;
MOV AH,1
INT 21H;等待输入一个数字字符0~9并送至AL
XOR AH,AH
AND AL,OFH;将AL中的数字字符转换为对应的数字,并送往AX
ADD AX,AX
ADD BX,AX;BX 指向第i个子程序入口
CALL WORD PTR[BX]; 近直接调用
MOV AH,4CH
INT 21H;返回DOS
SUB0 PROC
...
SUB0 ENDP
...
SUB9 PROC
...
SUB9 ENDP
CSEG 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 BX
PUSH CX
PUSHF
...
POPF
POP CX
POP BX
RET
SUB1 ENDP
(3) 保护现场与恢复现场的简便方法
在80286、80386及其以上指令集中具有将所有通用寄存器入栈及出栈的专用指令。如需要在子程序SUB1中保护与恢复AX、CX、DX、BX、SP、BP、SI、DI及FLAGS的内容,则可以用以下方法:
SUB1 PROC
PUSHA
PUSHF
...
POPF
POPA
RET
SUB1 ENDP
需要在子程序SUB1中保护与恢复EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI及EFLAGS的内容,则可以用以下方法:
SUB1 PROC
PUSHAD
PUSHFD
...
POPFD
POPAD
RET
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 SEGMENT
ARRAYB DB (若干个字节数)
COUNT EQU $-ARRAYB
ANS DW ?
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT USE 16;16位段
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
LEA SI,ARRAYB
MOV CX,COUNT ;约定寄存器SI、CX为子程序提供的入口参数
CALL ZNUM
MOV ANS,AX
MOV AH,4CH
INT 21H;返回DOS
ZNUM PROC
XOR AX,AX
NEXT:
CMP BYTE PTR[SI],0
JNZ NZ
INC AX
NZ:
INC SI
LOOP NEXT
RET
ZNUM ENDP
CSEG ENDS
ENDS START
- 约定存储单元法
约定存储单元法是指事先约定使用某些存储单元进行入口参数、出口参数的传递。
【例2】程序功能与【例1】同,但要求使用约定存储单元法实现参数的传递。
分析: 改用PARA1、PARA2单元传递入口参数,改用PARA3传递出口参数
DSEG SEGMENT
ARRAYB DB (若干个字节数)
COUNT EQU $-ARRAYB
ANS DW ?
PARA1 DW ?
PARA2 DW ?
PARA3 DW ?
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT USE 16;16位段
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
LEA SI,ARRAYB
MOV PARA1,SI
MOV PARA2,COUNT;通过约定的寄存单元PARA1和PARA2为子程序提供入口参数
CALL ZNUM
MOV AX,PARA3;通过约定的寄存器单元PARA3接受子程序提供的出口参数
MOV ANS,AX
MOV AH,4CH
INT 21H;返回DOS
ZNUM PROC
MOV SI,PARA1
MOV CX,PARA2;从约定的存储单元PARA1、PARA2中取入口参数
XOR AX,AX
NEXT:
CMP BYTE PTR[SI],0
JNZ NZ
INC AX
NZ:
INC SI
LOOP NEXT
MOV PARA3,AX
RET
ZNUM ENDP
CSEG ENDS
ENDS START
- 堆栈法
堆栈法是指通过堆栈进行入口参数、出口参数的传递。
【例3】编写程序,程序功能与【例1】同,但要求使用堆栈法实现参数的传递
分析:以下程序中,在调用程序的CALL指令前将入口参数压入堆栈,在子程序起始处将入口参数弹出堆栈以便子程序使用,即实现入口参数的传递;在子程序的RET指令前将出口参数压入堆栈,在调用程序的CALL指令后将出口参数弹出堆栈以便调用程序使用
DSEG SEGMENT
ARRAYB DB (若干个字节数)
COUNT EQU $-ARRAYB
ANS DW ?
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT USE 16;16位段
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
LEA SI,ARRAYB
MOV CX, COUNT
PUSH SI
PUSH CX;通过堆栈接受子程序提供的出口参数
CALL ZNUM
POP AX;通过堆栈接受出口参数
MOV ANS,AX
MOV AH,4CH
INT 21H;返回DOS
ZNUM PROC
MOV BP,SP
MOV CX,[BP+2]
MOV SI,[BP+4]
XOR AX,AX
NEXT:
CMP BYTE PTR[SI],0
JNZ NZ
INC AX
NZ:
INC SI
LOOP NEXT
MOV [BP+4],AX
RET 2
ZNUM ENDP
CSEG ENDS
ENDS START
7.3程序的嵌套和递归
7.3.1 子程序的嵌套
子程序可以调用另一个子程序,这种调用结构称为子程序的嵌套
【例1】 设BUF数据区有两个双字节无符号数,试求两者之和并将其存入SUM单元。然后将SUM单元的内容以十六进制形式在屏幕上显示出来。
分析:子程序SUB0为子程序SUB1提供数据区首址并调用SUB1;子程序SUB1实现两个双字节数的相加运算并存储结果,为子程序SUB2提供要显示的数据,并且调用SUB2;子程序SUB2实现数据的显示。
DSEG SEGMENT
BUF DW (两个双字节数)
SUM DW ?
DSEG ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
SUB0 PROC FAR
PUSH DS
MOV AX,0
PUSH AX;为返回DOS做准备
MOV AX,DSEG
MOV DS,AX
LEA SI,BUF;通过约定的寄存器SI为子程序SUB1提供入口参数
CALL SUB1
RET; 返回DOS
SUB0 ENDP
SUB1 PROC
MOV AX,[SI]
ADD AX,[SI+2]
MOV SUM,AX;存储结果,同时通过与欸的那个的存储单元SUM为子程序SUB2提供入口参数
CALL SUB2
RET;返回SUB0
SUB1 ENDP
SUB2 PROC
PUSH BX
PUSH CX
PUSH AX
PUSH DX
MOV BX,SUM
POP DX
MOV CH,4
POP AX
MOV CL,4
POP CX
NEXT:
ROL BX,CL
POP BX
MOV AL,BL
AND AL,0FH
RET;返回SUB1
ADD AL,30H
SUB2 ENDP
CMP AL,3AH
JB BA
CSEG ENDS
END SUB0
ADD AL,07H
BA: MOV DL,AL
MOV AH,02H
INT 21H;显示一位16进制数
DEC CH
JNZ NEXT
7.3.2 子程序的递归
子程序调用本身,或通过调用另一个子程序调用本身的结构称为子程序的递归。前者称为直接递归,后者称为间接递归
以下子程序采用了直接递归:
SUB1 PROC
...
CALL SUB1
...
RET
SUB1 ENDP
以下子程序采用了间接递归:
SUB1 PROC
...
CALL SUB2
...
SUB1 ENDP
SUB2 PROC
...
CALL SUB1
...
RET
SUB2 ENDP
【例1】编写计算N!的程序。假设。
N≤8,即N!不超出两字节无符号数的范围
分析: 可以在递归子程序中通过自身调用将N,N-1,…,1依次压入堆栈。这里是否已将1压入堆栈就是递归结束条件。一旦递归结束条件满足,则依次将堆栈中的1,2,…,N出栈相乘,从而算出N!
递归子程序如下:
FACT PROC
PUSH DX
MOV DX,AX
CMP AX,0; 判断递归结束的条件
JZ FR
DEC AX
CALL FACT; 计算(N-1)!
MUL DX;计算N!
POP DX;
RET
FR:
MOV AX,1
POP DX
RET
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 ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
XOR BL,BL; BL用于统计数字字符的个数
XOR CH,CH; CH用于统计字母字符的个数
XOR CL,CL; CL用于统计其他字符的个数
AGAIN:
MOV AH,1
INT 21H
CMP AL,0DH
JZ EXIT
CMP AL,'0'
JB OTHER
CMP AL,'9'
JA NEXT
INC BL
JMP AGAIN
NEXT:
CMP AL,'A'
JB OTHER
CMP AL,'Z'
JA OTHER
INC CH
JMP AGAIN
OTHER:
INC CL
JMP AGAIN
EXIT:
MOV DL,BL
CALL DISP
MOV DL,CL
CALL DISP
DISP PROC
ADD DL,30H
MOV AH,2
INT 21H
RET
DISP ENDP
CSEG ENDS
END 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 ENDS
SSEG SEGMENT
STACK DB 100H DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS: DSEG,SS:SSEG
START:
MOV AX,DSEG
MOV DS,AX
LEA DI,ASM
MOV CH,30
AGAIN:
CALL KEYIN; 调用键盘输入子程序,以输入十位数
MOV BL,AL
CALL KEYIN; 调用键盘输入子程序,以输入个位数
CALL A_B; 调用ASCII->BCD转换子程序生成压缩的BCD码存储
MOV [DI],AL ; 压缩的BCD码存入ASM的存储区
INC DI
DEC CH
JNZ AGAIN
LEA DI,ASM
MOV CX,30
CALL CMAX; 调用求最高得分的子程序
CALL B_A;调用BCD->ASCII转换子程序
CALL DISP;
MOV AH,4CH
INT 21H
KEYIN PROC
MOV AH,7
B_A PROC
INT 21H
MOV AL,MAX
RET
MOV BL,AL
KEYIN ENDP
AND AL,0F0H
MOV CL,4
SHR AL,CL
A_B PROC
ADD AL,30H
MOV CL,4
AND BL,0FH
SHL BL,CL
ADD BL,30H
SUB AL,30H
RET
ADD AL,BL
B_A ENDP
RET
A_B ENDP
DISP PROC
MOV DL,AL
CMAX PROC
MOV AH ,2
DEC CX
INT 21H
MOV AL,[SI]
MOV DL,BL
L1:
CMP AL,[SI+1]
MOV AH,2
JA NEXT
INT 21H
MOV AL,[SI+1]
RET
NEXT:
INC SI
DISP ENDP
LOOP L1
MOV MAX,AL
CSEG ENDS
RET
END STRAT
CMAX ENDP
参考
《汇编语言程序设计》丁辉主编