打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
[汇编]子程序结构
1子程序的嵌套

  我们已经知道,一个子程序也可以作为调用程序去调用另一个子程序,这种情况称为子程序的嵌套。嵌套的层次不限,其层数称为嵌套深度。动画表示了嵌套深度为2时的子程序嵌套情况。

  嵌套子程序的设计并没有什么特殊要求,除子程序的调用和返回应正确使用CALL和RET指令外,要注意寄存器的保存和恢复,以避免各层次子程序之间因寄存器冲突而出错的情况发生。如果程序中使用了堆栈,例如使用堆栈来传送参数等,则对堆栈的操作要格外小心,避免发生因堆栈使用中的问题而造成子程序不能正确返回的错误。

 2递归子程序

  在子程序嵌套的情况下,如果一个子程序调用的子程序就是它自身,就称为递归调用,这样的子程序称为递归子程序。
递归子程序对应于数学上对函数的递归定义,往往能设计出效率较高的程序,可完成相当复杂的计算,所以是很有用的。这里以阶乘函数为例,说明递归子程序的设计方法。

  例  编制计算N!(N>=0)的程序。
  N!= N* (N-1) * (N-2) * … * 1
  其递归定义如下:

  求N!本身是一个子程序,由于N!是N和(N-1)!的乘积,所以为求(N-1)!必须递归调用求N!的子程序,但每次调用所使用的参数都不相同。

  递归子程序的设计必须保证每次调用都不破坏以前调用时所用的参数和中间结果,所以一般把每次调用的参数、寄存器内容及所有的中间结果都存放在堆栈中。

  我们把一次调用所保存的信息称为帧(frame),一般一帧包括所保存的寄存器内容、参数或参数地址和中间结果等。每次调用把一帧信息存入堆栈。

  递归子程序中还必须包括基数的设置,当调用参数达到基数时,还必须有一条条件转移指令实现嵌套退出,以保证能按反向次序退出并返回主程序。

 
data_seg   segment
    n_v        dw    ?
    result      dw    ?
   data_seg   ends

   stack_seg  segment
     dw      128    dup(0)
     tos      label   word
   stack_seg   ends

  ;定义代码段code1
    code1    segment
     main    proc    far
     assume   cs:code1,ds:data_seg,ss:stack_seg
  start:
     mov     ax,stack_seg
     mov     ss,ax
     mov     sp,offset tos

     push    ds
     sub     ax,ax
     push    ax
     mov     ax,data_seg
     mov     ds,ax
  ; 程序的主要部分
     mov     bx,offset   result
     push    bx            ;result单元的地址入栈
     mov     bx,n_v
     push    bx ; n_v单元的内容入栈
     call    far      ptr fact  ;远调用子程序fact
     ret
     main    endp
  code1 ends

  ; 定义代码段code
     code    segment
  ; 定义frame帧结构数据
     frame    struc
     save_bp  dw      ?
     save_cs_ip dw      2 dup(?)
     n     dw      ?
     result_addr dw      ?
     frame    ends
     assume   cs:code
     fact    proc     far     ;定义子程序fact
     push    bp
     mov     bp,sp           ;bp用来指向帧结构
     push    bx
     push    ax
     mov     bx,[bp].result_addr    ;每帧中result_addr送bx
     mov     ax,[bp].n         ; 每帧中n送ax
     cmp     ax,0
     je     done
     push    bx            ;为下一次调用result_addr入栈
     dec     ax
     push    ax            ;为下一次调用n-1入栈
     call    far ptr fact
     mov     bx,[bp].result_addr
     mov     ax,[bx]
     mul     [bp].n          ; (ax)=n*result
     jmp     short     return
  done: mov     ax,1           ; (ax)=1
 return: mov     [bx],ax          ; result=(ax)
     pop     ax
     pop    bx
     pop    bp
     ret    4
     fact    endp
  code ends
  end  start

  从上述N!的递归子程序中可以看出,由于使用了STRUC伪操作,程序的结构更加清晰,也避免了计算参量地址时可能出现的错误。
左面给出了不用STRUC伪操作编制的求N!的递归子程序。

  在编制子程序时,特别是在编制嵌套或递归子程序时,堆栈的使用是十分频繁的。在这里顺便说明一下,在堆栈使用过程中,应该注意有关堆栈溢出的问题。

  由于堆栈区域是在堆栈定义时就确定了的,因而堆栈工作过程中有可能产生溢出。堆栈溢出有两种情况可能发生:如堆栈已满,但还想再存入信息,这种情况称为堆栈上溢;另一种情况是,如堆栈已空,但还想再取出信息,这种情况称为堆栈下溢。不论上溢或下溢,都是不允许的。因此在编制程序时,如果可能发生堆栈溢出,则应在程序中采取保护措施。这可以通过给SP规定上、下限,在进栈或出栈操作前先做SP和边界值的比较,如溢出则作溢出处理,以避免破坏其他存储区或使程序出错的情况发生。

 datarea    segment
      n     dw    ?
      result  dw    ?
   datarea    ends

   stack_seg   segment
      dw     128    dup(0)
      tos    label   word
   stack_seg   ends

   prognam    segment
      main    proc    far
      assume   cs:prognam,ds:datarea,ss:stack_seg
   start:
      mov    ax,stack_seg
      mov    ss,ax
      mov    sp,offset     tos

      push    ds
      sub    ax,ax
      push    ax
      mov    ax,datarea
      mov    ds,ax
   ; 程序的主要部分
      mov    bx,n
      push    bx
      call    fact             ;调用子程序fact
      pop    result
      ret
      main    endp

      fact    procnear           ; 定义子程序fact
      push    ax
      push    bp
      mov    bp,sp
      mov    ax,[bp+6]
      cmp    ax,0
      jne    fact1
      inc    ax
      jmp    exit
   fact1:
      dec    ax
      push    ax
      call    fact
      pop    ax
      mul    word     ptr[bp+6]
   exit: mov    [bp+6],ax
      pop    bp
      pop    ax
      ret
      fact    endp
  prognam ends
      end    start

   编制一个把十六进制数转换成十进制数的程序。要求把从键盘输入的0~FFFFH的十六进制正数转换为十进制数并在屏幕上显示出来。

  这一例子的功能是和例6.3相反的。它由HEXIBIN和BINIDEC两个主要的子程序组成,由于主程序和子程序在同一个程序模块中,因而省略了对寄存器的保护和恢复工作,子程序之间的参数传送则采用寄存器传送的方式进行。程序可用CtrlBreak退出。

   display    equ   2h     ; 显示单个字符的功能号是2
   key_in     equ    1h     ;键盘输入单个字符的功能号是1
   doscall    equ    21h     ; DOS中断号

  hexidec     segment
   main      proc    far
   assume     cs:hexidec
 start:
   push      ds
   sub      ax,ax
   push      ax

   call      hexibin      ; 十六进制转换成二进制
   call      crlf         ; 显示回车和换行
   call      binidec       ; 二进制转换成十进制
   call      crlf
   ……
   ret
   main      endp

   ; 定义子程序hexibin(十六进制转换成二进制,结果在bx中)
   hexibin    proc     near
   mov      bx,0
 newchar:
   mov      ah,key_in
   int      doscall       ; 键盘输入单个字符
   sub      al,30h
   jl       exit
   cmp      al,10d
   jl       add_to        ; 0~9之间转add_to
  ; 判断是否a~f之间('a'的ASCII码为61h)
   sub      al,27h
   cmp      al,0ah
   jl       exit
   cmp      al,10h
   jge      exit
  ; 0~9或a~f
 add_to:
   mov      cl,4
   shl      bx,cl        ; (bx)*16
   mov      ah,0
   add      bx,ax
   jmp      newchar
  exit:
   ret
   hexibin    endp

  ;定义子程序binidec(二进制转换成十进制)
   binidec    proc    near
   mov      cx,10000d
   call      dec_div       ; bx被10000除
   mov      cx,1000d
   call      dec_div       ; bx被1000除
   mov      cx,100d
   call      dec_div       ; bx被100除
   mov      cx,10d
   call      dec_div       ; bx被10除
   mov      cx,1d
   call      dec_div       ; bx被1除
   ret
   ; 定义子程序dec_div(十进制除)
   dec_div    proc    near
   mov      ax,bx
   mov      dx,0         ; 被除数在dx:ax中
   div      cx
   mov      bx,dx        ; 余数送bx
   mov      dl,al        ; 商送dl

   add      dl,30h
   mov      ah,display
   int      doscall       ; 显示单个字符
   ret
   dec_div    endp
   binidec    endp

   crlf      proc   near
   mov      dl,0ah
   mov      ah,display
   int      doscall

   mov      dl,0dh
   mov      ah,display
   int      doscall
   ret
   crlf      endp

   hexidec   ends
   end      start

  本例为一个简单的信息检索系统。在数据区里,有10个不同的信息,编号为0~9,每个信息包括30个字符。现在要求编制一个程序:从键盘接收0~9之间的一个编号,然后在屏幕上显示出相应编号的信息内容。

  在这个程序里,10个信息组成一个信息表,对信息表的查找是根据从键盘接收的编号来确定的。请注意从接收编号后到找到表格中所需区域为止的程序段,这也是查找表格的一种常用方法。此外,程序把显示一个信息编成一个独立功能的子程序DISPLAY,并把其中常用的显示一个字符的功能也编成一个子程序DISPCHAR,这样就使程序的结构更加清晰了。

   datarea    segment
    thirty    db    30
   ; 信息表
   msg0 db 'I like myIBM-PC---------------------------------------'
   msg1 db '8088 programming isfun--------------------------------'
   msg2 db 'Time to buy morediskettes-----------------------------'
   msg3 db 'This programworks-------------------------------------'
   msg4 db 'Turn off thatprinter----------------------------------'
   msg5 db 'I have more memory thanyou----------------------------'
   msg6 db 'The PSP can beuseful----------------------------------'
   msg7 db 'BASIC was easier thanthis-----------------------------'
   msg8 db 'DOS isindispensable-----------------------------------'
   msg9 db 'Last massage of theday--------------------------------'
   ; 错误信息
   errmsg db 'error!!! invalid parameter!! '
   datarea ends

   stack   segment
    db    256     dup(0)
    tos   label    word
   stack   ends

  prognam  segment
    main   proc     far
    assume  cs:prognam,ds:datarea,ss:stack
  start:
    mov    ax,stack
    mov   ss,ax
    mov    sp,offset  tos

    push   ds
    sub    ax,ax
    push   ax
    mov    ax,datarea
    mov   ds,ax

 begin:mov    ah,1
    int    21h         ; 从键盘输入一个字符
   ; 错误输入转error
    sub    al,'0'
    jc    error
    cmp    al,9
    ja    error
   ; 正确输入
    mov    bx,offset msg0    ; 信息表首地址送bx
    mul    thirty        ; (ax)=(al)*30
    add    bx,ax        ; bx指向相应位置
    call   display       ; 显示相应编号信息
    jmp    begin
 error:mov    bx,offset  errmsg
    call   display       ; 显示错误信息
    ret

    display proc    near
    mov    cx,30
 disp1:mov    dl,[bx]
    call   dispchar       ; 显示一个字符
    inc    bx
    loop   disp1        ; 循环
    mov    dl,0dh
    call   dispchar       ; 显示回车
    mov    dl,0ah
    call   dispchar       ; 显示换行
    ret
    display endp

    dispchar proc    near
    mov    ah,2
    int    21h
    ret
    dispchar endp
    main   endp
prognam ends
end   start

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
汇编语言之转移指令和原理
C语言调用汇编
启动ucosii之三PC
linux 0.11 内核学习
看雪学院
汇编(八)——数据传送类指令三
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服