Chinaunix首页 | 论坛 | 博客
  • 博客访问: 226389
  • 博文数量: 45
  • 博客积分: 1850
  • 博客等级: 上尉
  • 技术积分: 473
  • 用 户 组: 普通用户
  • 注册时间: 2005-07-11 10:21
文章分类
文章存档

2006年(17)

2005年(28)

我的朋友

分类:

2006-04-21 12:45:10

程序设计:在实际编程过程中常把功能相对独立的程序段单独编写和调试,作为一个相对独立的模块供程序使用,这就是子程序。子程序可以使源程序模块化,简化源程序结构,是子程序复用,提高编程效率。
一、过程定义伪指令
子程序在汇编语言中也被称为过程(Procedure),相当于高级语言中的过程和函数。每个子程序是具有唯一过程名的程序段。
过程名   PROC  [NEAR/FAR]
         过程体
过程名   ENDP
过程属性NEAR表示过程只能被相同代码段的其他程序调用,为段内调用;FAR表示可被相同或不同代码段的程序调用,为段间调用。对于简化段定义格式,在微型、小型和紧凑存储模式下,过程缺省为NEAR,中型、大型和巨型存储模式下,过程缺省为FAR。对于完整段定义格式,过程的缺省属性为NEAR。
过程的调用与返回是由CALL和RET来完成的。CALL的功能之一是将返回地址压入堆栈,当返回时,RET则直接从当前栈顶取内容作为返回地址,在进行过程设计时,必须注意存储器的保护与恢复。过程体中一般要使用寄存器,除了要带回结果的寄存器(返回参数)外,希望过程的执行不改变其他寄存器的内容,解决这个矛盾的常见方法是在过程开始部分先将要修改内容的寄存器顺序压栈(不包括返回值寄存器),在过程最后返回调用程序之前,再将寄存器内容逆序弹出。过程常具有类似如下的格式:
subname   proc
          push ax
          push bx
          push cx
          。。。
          pop cx
          pop bx
          pop ax
          ret
subname   endp
例27.1:编制一个过程把AL寄存器内的二进制数用十六进制形式在屏幕上显示出来。
分析:AL中8位二进制数对应2位十六进制数,先转换高4位成ASCII码并显示,然后转换低4位并显示。屏幕显示使用02号dos调用。
ALdisp   proc
         push ax
         push cx
         push dx
         push ax
         mov dl,al
         mov cl,4
         shr dl,cl
         or dl,30h
         cmp dl,39h
         jbe aldisp1
         add dl,7
aldisp1: mov ah,2
         int 21h
         pop dx
         and dl,0fh
         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
二、子程序的参数传递
主程序在调用子程序时,通常需要向其提供一些数据,对于子程序来说就是入口参数(输入参数);子程序结束返回给主程序必要的数据,这就是子程序的出口参数(输出参数)。主程序与子程序通过参数传递建立联系,传递参数的多少反映程序模块间的偶合程度。汇编语言中参数传递通过寄存器、变量或堆栈实现,参数的具体内容可以是数据本身(传数值)也可以是数据的存储地址(传地址)。子程序相对独立,在定义时要加上适当的注释,完整的注释应该包括子程序的功能、入口参数和出口参数等。
1、用寄存器传递参数
采用寄存器传递参数就是把参数存于约定的寄存器中,这种方法简单易行。
例27.2:设array是10个元素的数组,每个元素是8位数据。试用子程序计算数组元素的校验和,并将结果存入变量result中。所谓校验和是指不计进位的累加和,常用于检验信息的正确性。
分析:子程序完成元素求和,主程序需要向他提供入口参数,使得子程序能够访问数组元素。子程序需要回送求和结果这个出口参数。由于数组元素较多,直接用寄存器传送数据有困难,但是元素在主存中是顺序存放的,我们选用寄存器DS和BX传入数组首地址,用计数器CX传入数组元素个数。一个输出参数用累加器AL传出。
        .model small
        .stack 256
        .data
count   equ 10
array   db 12h,25h,0f0h,0a3h,3,68h,71h,0cah,0ffh,90h
result  db ?
        .code
        .startup
        mov bx,offset array
        mov cx,count
        call checksuma
        mov result,al
        .exit 0
        ;计算字节检验和的通用过程
        ;如口参数:DS:BX=数组的段地址:偏移地址,CX=元素个数
        ;出口参数:AL=校验和
        ;说明:除AX/BX/CX外,不影响其他寄存器
checksuma proc
        xor al,al
suma:   add al,[bx]
        inc bx
        loop suma
        ret
checksuma endp
        end
采用寄存器传送存储地址的方法在参数传递中常常使用,它可以传送较多的数据。采用寄存器传递参数,注意带有出口参数的寄存器不能保护和恢复,带有入口参数的寄存器可以保护,也可以不保护。
2、用变量传递参数
主程序与被调用过程直接使用同一个变量名访问传递的参数,就是利用变量传递参数。如果调用程序与被调用程序在同一个源程序文件,只要设置好数据段寄存器DS,则子程序与主程序访问变量的形式相同,也就是他们共享数据段的变量。调用程序与被调用程序不在同一个源文件中,必须利用public/extern进行声明,才能用变量传递参数。
例27.3:对27.2,改用变量传递参数、计算数组元素的检验和。
分析:采用变量传递参数,本例共有count、array和result变量。主程序只要设置数据段DS,就可以调用子程序,子程序直接采用变量名存取数组元素。
        ...
        .code
        .startup
        call checksumb
        .exit 0
        ;计算字节校验和
        ;如口参数:array=数组名,count=元素个数,result=校验和存放的变量名
checksumb proc
        push ax
        push bx
        push cx
        xor al,al
        mov bx,offset array
        mov cx,count
sumb:   add al,[bx]
        inc bx
        loop sumb
        mov result,al
        pop cx
        pop bx
        pop ax
        ret
checksumb endp
        end
利用变量传递参数,过程的通用性较差,然而在多个程序段间、尤其在不同的程序模块间,利用全局变量共享数据也是一种常见的参数传递方法。
3、利用堆栈传递参数
可以利用共享堆栈区,即利用堆栈传递参数。主程序将子程序的入口参数压入堆栈,子程序从堆栈中取出参数,子程序将出口参数压入堆栈,主程序弹出堆栈取得它们。
例27.4:对27.2,利用堆栈传递参数解决
分析:通过堆栈传递参数,主程序间数组的偏移地址和元素个数压入堆栈,然后调用子程序,子程序通过BP寄存器从堆栈相应位置取出参数(非栈顶数据),求和后用AL返回结果。由于共用数据段,所以没有传递数据段基址。
        ...
        .code
        .startup
        mov ax,offset array
        push ax
        mov ax,count
        push ax
        call checksumc
        add sp,4
        mov result,al
        .exit 0
        ;计算字节校验和的近过程
        ;如口参数:在堆栈压入数组的偏移地址和元素个数
        ;出口参数:al=校验和
checksumc  proc
         push bp
         mov bp,sp
         push bx
         push cx
         mov bx,[bp+6]
         mov cx,[bp+4]
         xor al,al
sumc:    add al,[bx]
         inc bx
         loop sumc
         pop cx
         pop bx
         pop bp
         ret
checksumc  endp
        end
上述程序执行过程中堆栈的利用情况如下图:
进入子程序后,设置基址指针BP等于当前堆栈指针SP,这样利用BP相对寻址(缺省利用SS段)可以存取堆栈段中的数据。主程序压入了2个参数,使用了堆栈区的4个字节;为了保持堆栈的平衡,主程序再用CALL指令后用一条“add sp,4”平衡堆栈。平衡堆栈也可以利用子程序实现,则返回指令采用“ret 4”,使SP加4。
堆栈由于采用了“先进后出”原则存取,而且返回地址和保护的寄存器等也要存于堆栈,因此利用堆栈传递参数时,要时刻注意堆栈的分配情况,保证参数的正确存取以及子程序的正确返回。
三、子程序的嵌套、递归与重入
1、子程序的嵌套
子程序内包含有子程序的调用就是子程序嵌套。嵌套深度(乔套的层次数)逻辑上没有限制,但由于子程序的调用需要在堆栈中保存返回地址以及寄存器等数据,因此实际上受限于开设的堆栈空间,嵌套设计除子程序的调用和返回应正确使用CALL和RET指令外,还要注意寄存器的保存和恢复,以避免各层子程序之间因寄存器使用冲突而出错。
在例27.1过程中有两段程序一样,可以写成过程,形成过程(子程序)嵌套:
Aldisp    proc
          push ax
          push cx
          push dx
          push ax
          mov dl,al
          mov cl,4
          shr dl,cl
          call dldisp
          pop dx
          and dl,0fh
          call dldisp
          pop dx
          pop cx
          pop ax
          ret
aldisp    endp
dldisp    proc
          or dl,30h
          cmp dl,39h
          jbe dldisp1
          add dl,7
dldisp1:  mov ah,2
          int 21h
          ret
dldisp    endp
2、子程序的递归
当子程序直接或间接的嵌套调用自身时称为递归调用,含有递归调用的子程序称为递归子程序。递归子程序的设计必须保证每次调用都不破坏以前调用时所用的参数和中间结果,因此将调用的输入参数、寄存器内容以及中间结果都存放在堆栈中。递归子程序必须采用寄存器或堆栈传递参数,递归深度受堆栈空间的限制。
例27.5:编制计算N!=N×(N-1)×(N-2)×...×2×1(N≥0)的程序
分析:递归定义┏
                      N!=┃N×(N-1)!  N>0
                           ┃1          N=0
                           ┗
求N!可以设计成输入参数为N的递归子程序,每次递归调用的输入参数递减1。如果N〉0,则由当前参数N乘以递归子程序返回值得到本层返回值,如果递归参数N=0,得到返回值1。
        .model small
        .stack 256
        .data
N       dw 3
result  dw ?
        .code
        .startup
        mov bx,N
        push bx
        call fact
        pop result
        .exit 0
fact    proc
        push ax
        push bp
        mov bp,sp
        mov ax,[bp+6]
        cmp ax,0
        jne fact1
        inc ax
        jmp fact2
fact1:  dec ax
        push ax
        call fact
        pop ax
        mul word ptr [bp+6]
fact2:  mov [bp+6],ax
        pop bp
        pop ax
        ret
fact    endp
        end
递归过程中的堆栈使用情况:
3、子程序的重入
子程序的重入是指子程序被中断后又被中断服务程序所调用,能够重入的子程序称为可重入子程序。在子程序中,注意利用寄存器和堆栈传递参数和存放临时数据,而不要使用固定的存储单元(变量),就能够实现重入。子程序的重入性在采用中断与外设交换信息的系统中是重要的,这样中断服务程序就可以调用这些可重入子程序而不致发生错误。dos功能调用是不可重入的。子程序的重入不同于子程序的递归。重入是被动地进入,而递归是主动进入,重入的调用间往往没有关系,而递归的调用间确是密切相关的。递归子程序也是可重入子程序。
阅读(2564) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~