子程序的设计方法
子程序又称为过程,相当于高级语言中的过程和函数。在一个程序的不同部分,往往要用到类似的程序段,即这些程序段的功能和结构形式都相同,只是某些变量赋值不同。此时就可以把这些程序段写成子程序形式,以便需要时调用它。
一、过程定义伪操作
  过程名 PROC Attribute
  ……
  过程名 ENDP
  其中,过程名是子程序入口的符号地址,与标号的作用相同。
  属性(Attribute)可以是NEARFAR
  过程属性的确定原则很简单,即:
  (1)如果调用程序和过程在同一个代码段中,则使用NEAR属性。
2)如果调用程序和过程不在同一个代码段中,则使用FAR属性。
二、过程的调用和返回
过程的调用和返回由CALLRET指令完成。
三、过程调用的注意事项
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            ; 从键盘接收单个字符
        ; 如输入非09之间的数则退出
     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小时内删除。