一.子程序设计
如果某个程序片段将反复在程序中出现,就把它设计成子程序
或某个程序片段具有通用性,可供许多程序共享,就把它设计成子程序
(一)过程调用和返回指令
调用前必须先压入堆栈。
过程调用指令有段内调用和段间调用之分。
过程返回指令也有段内返回和段间返回之分。
段内:近调用 近返回
段间:远调用 远返回
1.过程调用指令
过程调用指令先把子程序的返回地址(即CALL指令的下一条指令的地址)压入堆栈。
(1)段内直接调用
段内直接调用指令用于当前段内的子程序
CALL DST
该指令进行的具体操作分解如下:SP<---SP-2
[SP]<---IP
IP<---IP+disp
(2)段内间接调用
CALL OPRD
OPRD是16位通用寄存器或字存储器操作数
具体操作分解如下:
SP<---SP-2
[SP]<---IP
IP<---(OPRD)
例:
CALL BX
CALL WORD PTR [BX]
(3)段间直接调用
调用其他代码段中的子程序
CALL 过程名
具体操作的分解:
SP<---SP-2
[SP]<---CS
SP<---SP-2
[SP]<---IP
IP<---过程入口地址的偏移
例如:CALL FAR PTR SUBRO
(4)段间间接调用
CALL OPRD
OPRD是双字存储器操作数
CALL 过程名
具体操作的分解:
SP<---SP-2
[SP]<---CS
SP<---SP-2
[SP]<---IP
IP<---OPRD的低字值
CS<---OPRD的高字值
例如:CALL DWORD PTR [BX]
2.过程返回指令
过程返回指令把子程序的返回地址从堆栈弹出到IP或CS和IP
不影响标志位
(1)段内返回指令
RET
具体操作:
IP<---[SP]
SP<---SP+2
(2)段间返回指令
RET
具体操作:
IP<---[SP]
SP<---SP+2
CS<---[SP]
SP<---SP+2
(3)段内带立即数返回指令
RET 表达式
汇编程序把表达式的结果data取整
IP<---[SP]
SP<---SP+2
SP<---SP+data
该指令能同时修改堆栈指针。对堆栈的操作是以字为单位。data一般为偶数
(4)段间带立即数返回指令
RET 表达式
具体操作:
IP<---[SP]
SP<---SP+2
CS<---[SP]
SP<---SP+2
SP<---SP+data
(二)过程定义语句
利用过程定义伪指令语句,可把程序片段说明为具有近类型或远类型的过程
并能给过程起一个名字
过程名 PROC [NEAR|FAR] ;默认为NEAR
...
过程名 ENDP
过程名也有段值、偏移和类型三个属性
(三)子程序举例
已知程序如下
DATA SEGMENT
ORG 1000H
BLOCK DB 48H,8DH
RESULT DB ?
DATA ENDS
STACK SEGMENT PARA STACK 'STACK'
DB 64 DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,SS:STACK,DS:DATA
START PROC
BEGIN:MOV AX,DATA
MOV DS,AX
MOV SP,4000H
LEA BX,BLOCK
MOV AL,[BX]
SUB AL,[BX+1]
PUSH AX
PUSHF
CALL SUB1
POPF
POP AX
MOV [BX+2],AL
HLT
START ENDP
SUB1 PROC
ADD AL,AL
RET
SUB1 ENDP
CODE ENDS
END BEGIN
(四)子程序说明信息
为了正确使用子程序,在给出子程序代码时还要给出子程序的说明信息
子程序说明信息一般由如下部分组成:
(1)子程序名
(2)功能描述
(3)入口和出口参数
(4)所用的寄存器和存储单元
(5)使用的算法和重要的性能指标
(6)其他调用注意事项和说明信息
(7)调用实例
例:写一个把16位二进制数转换为4位十六进制ASCII码的子程序
;入口参数:DX = 欲转换的二进制数
;DS:BX = 存放转换所得ASCII码串的缓冲区首地址
;出口参数:十六进制数ASCII码串按高位到低依次存放在指定的缓冲区
二.主程序与子程序间的参数传递
1.寄存器传递法
把参数放在约定的寄存器中,适合用于传递参数较少的情况。
例子:把大写字母改成小写字母的子程序
;子程序名 UPTOLOW
;功能:大写转换为小写
;入口参数:AL=字符ASCII码
;出口参数:AL=字符ASCII码
;说明:如果字符是大写字母,就转换为小写。否则不变
UPTOLOW PROC
PUSHF ;保护各标志位
CMP AL,'A' ;与A比较,小于A则不是大写字母
JB UPTOLW1
CMP AL,'Z' ;与Z比较,大于A则不是大写字母
JA UPTOLW1
ADD AL,20H
UPTOLW1:POPF
UPTOLW ENDP
2.约定内存单元传递法
在传递参数较多的情况下,可利用约定的内存变量来传递参数
例子:
;子程序名:MADD
;功能:32位数相加
;入口参数:DATA1和DATA2缓冲区中分别存放要相加的32数
;出口参数:DATA3缓冲区中存放结果
;说明:(1)32位数据存放次序采用高高低低的原则
; (2)可能产生的进位存放在DATA3开始的第5字节中
MADD PROC
PUSH AX
PUSH CX
PUSH SI
MOV CX,2 ;循环
XOR SI,SI ;清零
MADD1:MOV AX,WORD PTR DATA1[SI]
ADC AX,WORD PTR DATA2[SI]
MOV WORD PTR DATA3[SI],AX
INC SI
INC SI
LOOP MADD1
MOV AL,0
ADC AL,0
MOV BYTE PTR DATA3+3,AL
POP SI
POP CX
POP AX
RET
MADD ENDP
3.利用堆栈传递参数
在调用子程序之前,把需要传递的参数依次压入堆栈,子程序从堆栈取入口参数。
如果使用堆栈传递出口参数,那么子程序在返回前,把需要返回的参数存入堆栈,
主程序在堆栈中取出口参数。
例:写一个测量字符串长度的子程序,设字符串以0为结束标志
;子程序名:STRLEN
;功能:测量字符串的长度
;入口参数:字符串起始地址的段值和偏移值在堆栈中
;出口参数:AX=字符串长度
STRLEN PROC
PUSH BP
MOV BP,SP
PUSH DS
PUSH SI
MOV DS,[BP+6]
MOV SL,[BP+4]
MOV AL,9
STRLEN1: CMP AL,[SI]
JZ STRLEN2
INC SI
JMP STRLEN1
STRLEN2:MOV AX,SI
SUB AX,[BP+4]
POP SI
POP DS
POP BP
RET
STRLEN ENDP
(不理解,第29集)
三.DOS功能调用及应用
1.DOS功能概述
MS-DOS内包含许多设计设备驱动和文件管理方面的子程序
DOS各种命令就是通过适当的调用这些子程序实现的。
为了方便程序使用,把这些子程序编写成相对独立的程序模块并编上号
这些编了号可由程序员调用的子程序就称为DOS的功能调用或系统调用
DOS功能调用主要包括三方面的子程序:
设备驱动(基本I/O)、文件管理和其他
2.调用方法
(1)根据需调用的功能调用准备入口参数,有部分功能不需要入口参数
(2)把功能调用号送AH寄存器
(3)发软中断指令INT 21H
3.基本I/O功能调用
(1)带回显键盘输入(1号功能调用)
(8号功能调用不带回显键盘输入)
功能:从标准输入设备上读一字符,并将该字符回显在标准输出设备上
如果键盘无字符可读,则一直等待有字符可读
AL=读到字符的代码(ASCII码)
(2)显示输出(2号功能)
功能:向标准输出设备写一字符。
Ctrl+C或Ctrl+Break结束程序
DL=要输出的字符
补充:
9号功能显示字符串
DS:DX=要输出字符串的首地址
以$结束
例子:利用2号系统功能调用完成输出线性一串字符信息
DATA SEGMENT
MSG DB 'THIS MESSAGE'
MSGLEN EQU $-MSG ;当前地址减去字符串的首地址就是字符串的长度
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
BEGIN:MOV AX,DATA
MOV DS,AX
MOV CX,MSGLEN
MOV SI,0
MOV AH,02 ;把功能号2送到AH
NEXT:MOV DL,MSG[SI] ;输出的字符放在DL中
INT 21H ;输出一个字符
INC SI
LOOP NEXT
MOV AH,4CH
INT 21H ;返回DOS状态
CODE ENDS
END BEGIN
如果是9号功能
必须把MSG放入DS:DX首地址
定义字符串必须以$结束
输出不需要放入DL中
4.递归子程序
必须用寄存器或堆栈传递参数,递归深度受堆栈空间的限制
例子:递归实现求阶乘
;子程序名:FACT
;功能:计算N!
;入口参数(AX)=n
;出口参数(AX)=n!
;说明(1)采用递归算法实现求阶乘
;(2)n不能超过8
FACT PROC
PUSH DX
MOV DX,AX
CMP AX,0 ;N为0?
JZ DONE ;是,转
DEC AX ;否,n-1
CALL FACT ;求(n-1)!
MUL DX ;n*(n-1)
POP DX
RET
DONE:MOV AX,1 ;0!=1
POP DX
RET
FACT ENDP