</div>
子程序是由设计者定义的完成某种功能的程序模块。一旦定义了,该子程序可被任意调用。 例
|
SENDAT PROC FAR ;子程序定义伪指令语句 |
|
PUSH AX ;保护 AX 、 DX 、 SI 寄存器 |
|
PUSH DX |
|
PUSH SI |
|
LEA SI , BUFR ;子程序待输出的数据的首地址 |
|
GOON : MOV DX , 03FBH |
|
WAIT : IN AL , DX ;读端口 03FBH 读入数据 |
|
TEST AL , 20H |
|
JZ WAIT |
|
MOV AL , [SI] ;将缓冲区 BUFR 按字节装入 |
|
MOV DX , 03F 8H |
|
OUT DX , AL ;输出至端口 03F 8H |
|
INC SI |
|
CMP AL , 0AH ;判断输出数据是否为结束 |
|
JNE GOON ;不为 0AH 则转至 GOON |
|
POP SI ;恢复寄存器 |
|
POP DX |
|
POP AX |
|
RET |
|
SENDAT ENDP |
上面的子程序,可以把数据段 BUFR 缓冲区,以 OA 结束的数据,输出到 03F 8H 端口。 主程序在调用子程序时,一方面初始数据要传递给子程序,另一方面子程序运行的结果要传递给主程序。尽管没有初始数据或没有运行结果的情况也有,但一般情况下是必须考虑的。 在编写较为复杂的子程序时,可能出现子程序中调用子程序的情况,通常称这种情况叫子程序嵌套。子程序嵌套层次的深度受堆栈大小的影响,由于堆栈不仅在子程序中使用,还受多方面影响,必须保证整个程序运行过程中,堆栈不能溢出。 把功能相对独立的程序段单独编写和调试,作为一个相对独立的模块供程序使用,就形成子程序子程序可以实现源程序的模块化,可简化源程序结构,可以提高编程效率 1、程序定义伪指令 格式 : 过程名 proc [near|far] . 过程名 endp 过程名(子程序名)为符合语法的标识符 NEAR 属性(段内近调用)的过程只能被相同代码段的其他程序调用 FAR 属性(段间远调用)的过程可以被相同或不同代码段的程序调用 对简化段定义格式,在微型、小型和紧凑存储模式下,过程的缺省属性为 near ;在中型、大型和巨型存储模式下,过程的缺省属性为 far 对完整段定义格式,过程的缺省属性为 near 用户可以在过程定义时用 near 或 far 改变缺省属性
子程序常见格式: subname proc ; 具有缺省属性的 subname 过程 push ax ; 保护寄存器:顺序压入堆栈 push bx ;ax/bx/cx 仅是示例 push cx … ; 过程体 , 程序的主要功能 pop cx ; 恢复寄存器:逆序弹出堆栈 pop bx pop ax ret ; 过程返回 subname endp ; 过程结束 ; 子程序功能:实现光标回车换行 dpcrlf proc ; 过程开始 push ax ; 保护寄存器 AX 和 DX push dx mov dl,0dh ; 显示回车 mov ah,2 int 21h mov dl,0ah ; 显示换行 mov ah,2 int 21h pop dx ; 恢复寄存器 DX 和 AX pop ax ret ; 子程序返回 dpcrlf endp ; 过程结束 例 : 无参数传递的子程序 ALdisp proc ; 实现 al 内容的显示 push ax ; 过程中使用了 AX 、 CX 和 DX push cx push dx push ax ; 暂存 ax mov dl,al ; 转换 al 的高 4 位 mov cl,4 shr dl,cl or dl,30h ;al 高 4 位变成 3 cmp dl,39h jbe aldisp1 add dl,7 ; 是 0Ah ~ 0Fh ,还要加上 7 aldisp1: mov ah,2 ; 显示 int 21h 例 : 实现 AL 内容显示的子程序 pop dx ; 恢复原 ax 值到 dx and dl,0fh ; 转换 al 的低 4 位 or dl,30h cmp dl,39h jbe aldisp2 add dl,7 aldisp2: mov ah,2 ; 显示 int 21h pop dx pop cx pop ax ret ; 过程返回 ALdisp endp ... ; 主程序 mov bx,offset array; 调用程序段开始 mov cx,count displp: mov al,[bx] call ALdisp ; 调用显示过程 mov dl,',' ; 显示一个逗号,分隔数据 mov ah,2 int 21h inc bx loop displp ; 调用程序段结束 .exit 0 ... ; 过程定义 end
HTOASC proc ; 将 AL 低 4 位表达的一位 16 进制数转换为 ASCII 码 and al,0fh cmp al,9 jbe htoasc1 add al,37h ; 是 0AH ~ 0FH ,加 37H ret ; 子程序返回 htoasc1: add al,30h ; 是 0 ~ 9 ,加 30H ret ; 子程序返回 HTOASC endp 2、子程序的参数传递 入口参数(输入参数):主程序提供给子程序 出口参数(输出参数):子程序返回给主程序 参数的形式: ① 数据本身(传值) ② 数据的地址(传址) 传递的方法: ① 寄存器 ② 变量 ③ 堆栈 例:求校验和 子程序计算数组元素的“校验和” 校验和是指不记进位的累加 入口参数: 数组的逻辑地址(传址) 元素个数(传值) 出口参数: 求和结果(传值) 把参数存于约定的寄存器中,可以传值,也可以传址。 子程序对带有出口参数的寄存器不能保护和恢复(主程序视具体情况进行保护) 子程序对带有入口参数的寄存器可以保护,也可以不保护;但最好一致 例 : 入口参数: CX =元素个数, DS:BX =数组的段地址:偏移地址 出口参数: AL =校验和 用寄存器传递参数 .startup ; 设置入口参数(含有 DS ←数组的段地址) mov bx,offset array ;BX ←数组的偏移地址 mov cx,count ;CX ←数组的元素个数 call checksuma ; 调用求和过程 mov result,al ; 处理出口参数 .exit 0 checksuma proc xor al,al ; 累加器清 0 suma: add al,[bx] ; 求和 inc bx ; 指向下一个字节 loop suma ret checksuma endp end 主程序和子程序直接采用同一个变量名共享同一个变量,实现参数的传递 不同模块间共享时,需要声明 例 : 入口参数: count =元素个数, array =数组名(含段地址:偏移地址) 出口参数: result =校验和 用变量传递参数 ; 主程序 call checksumb
; 子程序 checksumb proc push ax push bx push cx xor al,al ; 累加器清 0 mov bx,offset array ;BX ←数组的偏移地址 mov cx,count ;CX ←数组的元素个数 sumb: add al,[bx] ; 求和 inc bx loop sumb mov result,al ; 保存校验和 pop cx pop bx pop ax ret checksumb endp 主程序将子程序的入口参数压入堆栈,子程序从堆栈中取出参数 子程序将出口参数压入堆栈,主程序弹出堆栈取得它们 例 : 入口参数: 顺序压入偏移地址和元素个数 出口参数: AL =校验和 用堆栈传递参数 .startup mov ax,offset array push ax mov ax,count push ax call checksumc add sp,4 mov result,al .exit 0 要注意堆栈的分配情况,保
|