《汇编语言程序设计实验指导及习题解答-第五章》由会员分享,可在线阅读,更多相关《汇编语言程序设计实验指导及习题解答-第五章(23页珍藏版)》请在金锄头文库上搜索。
1、 第 5 章 子程序及宏指令设计 子程序及宏指令是汇编语言程序设计中的重要内容,可以简化程序结构,实现程序的 模块化,提高汇编语言程序设计的质量和效率。本章主要介绍了子程序的定义、子程序的 调用和返回、子程序的参数传递方法以及宏汇编中最具特色的部分:宏指令、重复汇编与 条件汇编,并结合具体实例,讨论了子程序和宏指令的程序设计方法及技巧。 5.1 子程序设计方法 子程序是程序设计的基本概念。实际编程时,常把功能相对独立的程序段单独编写和 调试,作为一个相对独立的模块供程序使用,这就是子程序,亦称过程,相当于高级语言 中的过程和函数,调用子程序的程序称为主程序(或称为调用程序) 。 5.1.1 子
2、程序定义 子程序的定义是由一对过程定义伪指令 PROC 和 ENDP 来完成的,其一般格式如下: 子程序名 PROCNEARFAR 保护现场 子程序体 恢复现场 RET 子程序名 ENDP 对子程序定义的具体规定如下: (1) “子程序名”必须是一个合法的标识符,并且二者要前后一致; (2)PROC 和 ENDP 必须是成对出现的关键字,它们分别表示子程序定义的开始和 结束; (3)子程序要有一条返回指令,返回指令是子程序的出口语句; (4)子程序的类型有近(NEAR) 、远(FAR)之分,其默认的类型是近类型,如果一 个程序要被另一段程序调用,那么,其类型应定义为 FAR,否则,其类型可以是
3、 NEAR。 显然,NEAR 类型的子程序只能被与其同段的程序所调用。下面举例说明其使用方法。 1调用程序和子程在同一个代码段的程序结构 【例 5.1】 代码段中含有主程序和一个子程序的情况,实际上可以含有多个子程序, 子程序类型可以是 NEAR 或省略。 CODE SEGMENT MAIN PROC FAR 汇编语言程序设计(第2版) 94 CALL SUB1 RET MAIN ENDP SUB1 PROC NEAR RET SUB1 ENDP CODE ENDS END MAIN 2调用程序和子程序在不同段的程序结构 【例 5.2】 调用程序和子程序不在同一个代码段,其中的子程序为 FAR
4、 类型,CALL 指令要显示说明是 FAR 类型。 CODE1 SEGMENT MAIN PROC FAR CALL FAR PTR SUB1 CALL FAR PTR SUB2 RET MAIN ENDP CODE1 ENDS CODE2 SEGMENT SUB1 PROC FAR CALL SUB2 RET SUB1 ENDP SUB2 PROC FAR RET SUB2 ENDP CODE2 ENDS END MAIN 若一个子程序既被段间调用又被段内调用, 则其类型必须是 FAR, 如例 5.2 中的 SUB2。 5.1.2 寄存器内容的保存及恢复 由于 CPU 中的寄存器数量有限,所
5、以主程序和子程序所使用的寄存器往往会发生冲子程序及宏指令设计 95第 5 章突。如果主程序在调用子程序之前的某个寄存器内容再从子程序返回后还有用,而子程序 又恰好使用了同一个寄存器, 这就破坏了该寄存器的原有内容, 因而会造成程序运行错误, 这是不允许的。因此为了保证子程序返回主程序时主程序能继续执行,就必须注意主程序 现场的保护。现场是指主程序转到子程序前这一时刻主程序所使用的资源或状态,如标志 寄存器、通用寄存器及存储器单元的内容。通常在转到子程序前将它们压入堆栈,以免子 程序在执行时使用这些资源而发生冲突。而当子程序返回主程序时,主程序的现场必须恢 复,即把它们从堆栈中弹出,保持原来的内
6、容不被改变,主程序才能正确地继续执行。保 护与恢复现场的工作通常安排在子程序中进行,在子程序的开始处安排一串保护现场的语 句,在子程序返回前,再恢复有关内容。例如: SUB1 PROC NEAR PUSH AX PUSH BX PUSH CX PUSH DX POP DX POP CX POP BX POP AX SUB1 ENDP 在子程序设计时,应仔细考虑哪些寄存器是必须保存的,哪些寄存器是不必要或不应 保存的。一般说来,子程序中用到的寄存器是应该保存的。但是,如果使用的寄存器在主 程序和子程序之间传送参数的话,则这种寄存器就不一定需要保存,特别是用来向主程序 传送结果的寄存器,就更不应该
7、因保存和恢复寄存器而破坏了应该向主程序传送的信息。 5.1.3 子程序的调用及返回 子程序的调用及返回是通过 CALL 和 RET 指令实现的。应特别注意的是,为保证子 程序的正确调用与返回,除定义时需正确选择属性外,还应该注意子程序运行期间的堆栈 状态。当发生过程调用时,CALL 指令的功能之一是将返回地址压入堆栈,当子程序返回 时,RET 则直接从当前栈顶取内容作为返回地址,而子程序中可能还有其他指令涉及堆栈 操作。 因此, 要保证 RET 指令执行前堆栈栈顶的内容刚好是过程返回的地址, 即相应 CALL 指令压栈的内容,否则将造成不可预测的错误。 关于子程序的调用有两种特殊的情况,即子程
8、序嵌套调用和子程序递归调用。在子程 序调用其他子程序时,称为子程序嵌套,只要堆栈空间允许,嵌套层次不限。若子程序中 又调用该子程序本身则称为递归调用,递归的深度亦与堆栈大小有关。 5.1.4 子程序的参数传递 子程序一般是完成某种特定功能的程序段。当一个程序调用一个子程序时,通常都向 子程序传递若干个数据让它来处理,当子程序处理完后,一般也向调用它的程序传递处理汇编语言程序设计(第2版) 96 结果,这种在调用程序和子程序之间的信息传递称为参数传递。调用程序向子程序传递的 参数称为子程序的入口参数,子程序向调用它的程序传递的参数称为子程序的出口参数。 调用程序与子程序间通过参数传递建立联系,相
9、互配合共同完成处理工作。传递参数 的多少反映程序模块间的耦合程度。根据实际情况,子程序可以只有入口参数或只有出口 参数,也可以同时存在入口参数和出口参数。参数的具体内容可以是数据本身(传数值) 也可以是数据的存储地址(传地址) 。方便灵活的参数传递是子程序设计的关键环节之一。 在汇编语言中,常用的 3 种参数传递方法包括利用寄存器传递参数、通过地址表传递 参数地址和利用堆栈传递参数。下面分别进行讨论。 1利用寄存器传递参数 由于 CPU 中的寄存器在任何程序中都是“可见”的,一个程序对某寄存器赋值后,在 另一个程序中就能直接使用,所以用寄存器来传递参数最直接、简便,也是最常用的参数 传递方式。
10、 但由于 CPU 中寄存器的个数和容量都是非常有限的, 所以该方法适用于传递较 少的参数信息。 主程序在调用子程序前,先将需要传递的参数保存在某些通用寄存器中,然后再调用 子程序,这样,子程序就可直接从寄存器中获得入口参数。同样,出口参数可以通过寄存 器返回给主程序。 【例 5.3】 在字节型变量 BCDBUF 中有一个组合 BCD 码,试将其转换为二进制数后 存入 BINBUF 单元中。 将组合 BCD 码分离出相应于十进制数的十位和个位,在进行十位数乘以 10 加上个位 数的运算即可得到对应的二进制码。 .MODEL SMALL .STACK .DATA BCDBUF DB 65H BIN
11、BUF DB ? .CODE START: MOV AX,DATA MOV DS,AX MOV AL,BCDBUF ;将要传递的参数放在寄存器 AL 中 CALL TRAN MOV BINBUF,AL ;返回结果在 AL 中 MOV AX,4C00H INT 21H TRAN PROC NEAR PUSHF PUSH BX PUSH CX MOV AH,AL AND AH,OFH ;分离出个位数 MOV BL,AH AND AL,0F0H ;分离出十位数 子程序及宏指令设计 97第 5 章MOV CL,04H ROR AL,CL ;将十位数移至低 4 位 MOV BH,0AH MUL BH ;
12、十位数乘以 10 ADD AL,BL ;乘积与个位数相加 POP CX POP BX POPF RET TRAN ENDP END START 2通过地址表传递参数地址 有时直接传递参数本身不能方便地实现所要求功能,需要通过地址表传递参数地址的 方法实现参数传递。具体方法是先建立一个地址表,该表由参数地址构成。然后把表的首 地址通过寄存器或堆栈传递给子程序。 【例 5.4】 计算 ARY1、ARY2、ARY3 三个数组和,并把各自的和分别放入 SUM1、 SUM2、SUM3 单元,其数组元素及结果均为字型数据。 显然数组求和应该用子程序完成,这样做使得代码真正共享。但在子程序中不能直接 引用数
13、组变量名,否则不能做到通用,在这种情况下可以通过传递参数地址的方法实现最 终的参数传递。本例用数组首地址、元素个数的地址、结果地址构成一个地址表,通过寄 存器把表的首地址传递给子程序,子程序通过地址表的参数地址访问到所需参数。 .MODEL SMALL .STACK .DATA ARY1 DW 1,2,3,4,5,6,7,8,9,10 COUNT1 DW(-ARY1)/2 SUM1 DW ? ARY2 DW 10,20,30,40,50,60,70,80 COUNT2 DW(-ARY2)/2 SUM2 DW ? ARY3 DW 100,200,300,400,500,600 COUNT3 DW
14、(-ARY3)/2 SUM3 DW ? TABLE DW 3 DUP(?) .CODE MOV AX,DATA MOV DS,AX MOV TABLE,OFFSET ARY1 ;构造数组 1 的地址表 MOV TABLE+2,OFFSET COUNT1 MOV TABLE+4,OFFSET SUM1 LEA BX,TABLE ;通过寄存器传递地址表的首地址 汇编语言程序设计(第2版) 98 CALL ARY-SUM ;求和数组 1 并保存结果 MOV TABLE,OFFSET ARY2 ;构造数组 2 的地址表 MOV TABLE+2,OFFSET COUNT2 MOV TABLE+4,OFFSET SUM2 LEA BX,TABLE ;通过寄存器传递地址表的首地址 CALL ARY-SUM ;求和数组 2 并保存结果 MOV TABLE,OFFSET ARY3 ;构造数组 3 的地址表 MOV TABLE+2,OFFSET COUNT3 MOV TABLE+4,OFFSET SUM3 LEA BX,TABLE ;通过寄存器传递地址表的首地址 CALL