子程序的设计方法
子程序又称为过程,相当于高级语言中的过程和函数。在一个程序的不同部分,往往要用到“类似”的程序段,即这些程序段的功能和结构形式都相同,只是某些变量赋值不同。此时就可以把这些程序段写成子程序形式,以便需要时调用它。
一、过程定义伪操作
过程名 PROC Attribute
……
过程名 ENDP
其中,过程名是子程序入口的符号地址,与标号的作用相同。
属性(Attribute)可以是NEAR或FAR。
过程属性的确定原则很简单,即:
(1)如果调用程序和过程在同一个代码段中,则使用NEAR属性。
(2)如果调用程序和过程不在同一个代码段中,则使用FAR属性。
二、过程的调用和返回
过程的调用和返回由CALL和RET指令完成。
三、过程调用的注意事项
1、过程属性要正确选择。
2、由于CALL指令使断点地址入栈,而RET指令使断点地址出栈。如果子程序不能正确使用堆栈而造成RET指令执行前SP并未指向断点地址,则必然会导致运行出错,因此子程序中对堆栈的使用应该特别小心,以免发生错误。
3、必要时应保存与恢复寄存器。如果子程序调用之前的某个寄存器内容在子程序调用完后还有用,而子程序恰好会使用该寄存器,这就会破坏该寄存器的原有内容,从而造成程序运行错误。为避免这种错误的发生,在一进入子程序后,就应该把子程序所需要使用的寄存
器的内容保存在堆栈中,而在退出子程序前把寄存器内容恢复原状。但是,如果通过寄存器在调用程序和子程序之间传送参数的话,则这些寄存器就不一定需要保存,特别是用来向主程序回送结果的寄存器,就更不应该因保存和恢复寄存器而破坏了应该向调用程序传送的信息。
四、调用程序和子程序之间的参数传送
调用程序在调用子程序时,经常需要传送一些参数给子程序;子程序运行完后也经常要回送一些信息给调用程序。这种调用程序和子程序之间的信息传送称为参数传送。调用程序和子程序之间的参数传送方式有以下几种:
1、通过寄存器传送参数
这是最常用的一种方式,使用方便,但参数较多时不能使用这种方法,因为寄存器个数有限。
例1:十进制到十六进制数转换程序。程序要求从键盘取得一个十进制数(假定输入的数字大于等于0且小于216),然后把该数以十六进制形式在屏幕上显示出来。
采用子程序结构。用一个子程序DEC2BIN实现从键盘取得十进制数并把它转换为二进制数;另一个子程序BIN2HEX把此二进制数以十六进制数的形式在屏幕上显示出来。另外,用CRLF子程序取得回车和换行的效果。在这里,各个子程序之间用BX调用子程序的例子寄存器来传送信息。Dec2hex segment
assume cs:dec2hex
; 程序的主要部分
Main proc far
repeat: call decibin ; 调用子程序dec2bin
call crlf ; 调用子程序crlf
call bin2hex ; 调用子程序bin2hex
call crlf
jmp repeat
main endp
; 子程序dec2bin
Dec2bin proc near
Mov bx,0 ; BX存放已输入的十进制数转换成的十六进制数的
newchar: mov ah,1
int 21h ; 从键盘接收单个字符
; 如输入非0~9之间的数则退出
Sub al,30h
Jl exit
Cmp al,9d
Jg exit
Cbw ; al扩展到ax
; BX中的数乘以10
xchg ax,bx
mov cx,10d
mul cx
xchg ax,bx
; 把ax加到bx中
Add bx,ax
Jmp newchar ; 接收下一个字符
exit: ret
decibin endp
; 子程序bin2hex
Bin2hex proc near
Mov ch,4
rotate: mov cl,4
rol bx,cl
mov al,bl
and al,0fh
add al,30h
cmp al,3ah
jl printit
add al,7h
printit: mov dl,al
mov ah,2
int 21h
dec ch
jnz rotate
ret
bin2hex endp
; 子程序crlf
crlf proc near
; 显示回车
mov dl,0dh
mov ah,2
int 21h
; 显示换行
Mov dl,0ah
mov ah,2
int 21h
ret
crlf endp
dec2hex ends
end main
2、通过变量传送参数
如子程序和调用程序在同一源文件(同一程序模块)中,调用程序可把参数存放在变量中,子程序直接访问变量,从而达到参数传送的目的。
例2:主程序MAIN和子程序PROADD在同一源文件中,要求用子程序PROADD累加数组ary中的100个字元素,并把和(不考虑溢出的可能性)送到指定的存储单元sum中去。在这里,子程序PROADD直接访问模块的数据段。
Data segment ; 定义数据段
Ary dw 100 dup(?)
Count dw 100
Sum dw ?
Data ends
Code segment ; 定义代码段
main proc far ; 主过程MAIN
assume cs:code, ds:data
start:
……
call near ptr proadd ; 调用子程序proadd
……
ret
main endp
; 子程序proadd
Proadd proc near
Push ax ; 保存寄存器
Push cx
Push si
Lea si,ary ; 数组ary的首地址送si
Mov cx,count
Xor ax,ax
next: add ax,[si]
add si,2
loop next
mov sum,ax ; 累加和送sum单元
pop si ; 恢复寄存器
pop cx
pop ax
ret
proadd endp
code ends
end start
3、不同程序模块间过程调用时的参数传送暂不讨论。
4、通过地址表传送参数地址
在数据段中建立一地址表,主程序在调用子程序前把要传送给子程序的参数都存放在地址表中,然后把地址表的首地址通过某个寄存器传给子程序。子程序通过地址表取得所需参数。
对于例2,如采用地址表传送参数地址的方式,只需先在数据段中定义表TABLE并在PROADD子程序调用前增加下述指令。
TABLE定义如下所示:
TABLE DW ?, ?, ?
PROADD子程序调用前需增加的指令如下所示:
MOV TABLE, OFFSET ARY
MOV TABLE+2, OFFSET COUNT
MOV TABLE+4, OFFSET SUM
MOV BX, OFFSET TABLE ;通过BX向子程序传递TABLE首地址
CALL PROADD
完整的程序如下:
Data segment ; 定义数据段
Ary dw 100 dup(?)
Count dw 100
Sum dw ?
Table DW ?, ?, ? ; 地址表
Data ends
prog_ seg segment
assume cs:prog_seg,ds:prog_seg,ss:prog_seg
main proc near ; 主程序
start: mov ax,prog_seg
mov ds,ax
……
; 初始化地址表TABLE
mov table,offset ary
mov table+2,offset count
mov table+4,offset sum
mov bx,offset table ; 地址表首地址送bx
call proadd ; 调用子程序proadd
……
Mov ax,4c00h
Int 21h
Main endp
; 子程序proadd
Proadd proc near
Push ax ; 保存寄存器
Push cx
Push si
push di
mov si,[bx] ; 数组ary首地址送si
mov di,[bx+2] ; count单元地址送di
mov cx,[di] ; count单元内容送cx
mov di,[bx+4] ; sum单元地址送di
xor ax,ax
next: add ax,[si]
add si,2
loop next
mov [di],ax ; 累加和送sum单元
pop di ; 恢复寄存器
pop si
pop cx
pop ax
ret
proadd endp
prog_seg ends
end start
5、通过堆栈传送参数或参数地址
主程序在调用子程序之前把参数/参数地址保存到堆栈中,子程序从堆栈中取出参数,从而达到传送参数/参数地址的目的。必须注意,子程序结束时的RET指令应使用带常数的返回指令,使RET在恢复断点时将主程序压入堆栈的参数弹出丢掉,从而使堆栈恢复为子程序调用前的状态。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论