《第六章子程序结构.ppt》由会员分享,可在线阅读,更多相关《第六章子程序结构.ppt(30页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、6.1子程序调用与返回6.2寄存器保护与恢复6.3过程定义6.4 子程序参数传递:寄存器、内存、堆栈6.5 子程序举例 第六章子程序结构 在编写汇编语言源程序时,把实现独立功能的程序段编写为子程序。在主程序中调用子程序,可以改善程序的可读性和可维护性。子程序中至少要有一条RET指令,主程序一般应通过CALL 指令调用子程序,。如图:主程序 子程序MOV AX,10H .CALL XXX MOV CX,BX .CALL XXX MOV CX,BX .XXX:SHL AX,1 .RET 6.1 子程序调用与返回 在编程时容易犯的错误:正确的方法:MOV AX,10H .CALL XXX MOV C
2、X,BX .CALL XXX XXX:SHL AX,1 .RET MOV AX,10H .CALL XXX MOV CX,BX .CALL XXX HLT XXX:SHL AX,1 .RET 在编程时容易犯的错误:正确的方法:MOV CX,10H .SNT:CALL YYY ADD AX,BX .YYY:SHL AX,1 .RET LOOP SNT HLT MOV CX,10H .SNT:CALL YYY ADD AX,BX .LOOP SNT HLT YYY:SHL AX,1 .RET 通过CALL指令执行子程序,为什么在执行RET指令时能够返回到CALL指令的下一条指令?是因为在执行CAL
3、L指令时,把返回地址保存在了堆栈中,在子程序的末尾,RET指令从堆栈中取出返回地址,从而返回到原来CALL指令的下一条指令执行。以段内调用段内调用子程序为例,CALL指令执行时堆栈变化如下:SP SP-2 (SS:SP)IP 在子程序末尾执行段内返回段内返回 RET操作如下:IP(SS:SP)SP SP2 下面看演示:CALL指令有段内调用和段间调用两种,段间调用段间调用 CALL指令执行时堆栈变化如下:SP SP-2 (SS:SP)CS SP SP-2 (SS:SP)IP 在子程序末尾执行段间返回段间返回 RET操作如下:IP (SS:SP)SP SP2 CS(SS:SP)SP SP2下面看
4、演示:在子程序中有时候需要使用寄存器,有时候会与主程序使用寄存器发生冲突,这就需要在进入子程序后保护这些寄存器,而在子程序结束时恢复这些寄存器的内容。例如:要求用右移的方法把数组ARRY中的20个元素除以8,元素是16位有符号数。我们把16位有符号数除以8的部分编写成子程序DIV8:子程序名:DIV4入口参数:AX中为16位有符号数出口参数:AX中为入口参数的八分之一DIV:MOV CL,3 SAR AX,CL RET 6.2 寄存器保护与恢复主程序段:MOV BX,OFFSET ARRY MOV CX,20D8Y:MOV AX,BX CALL DIV8 ;AX内容除以8 MOV BX,AX
5、INC BX INC BX LOOP D8Y ;后续程序可以看到,上面主程序使用了CX,而子程序也使用了CL,发生了冲突,因此在调用子程序时,应该保护CX。可以在主程序中保护,也可以在子程序中保护。在主程序中保护:MOV BX,OFFSET ARRY MOV CX,20D4Y:MOV AX,BX PUSH CX CALL DIV4 POP CX MOV BX,AX INC BX INC BX LOOP D4Y ;后续程序 也可以在子程序内部保护寄存器,即在入口处保护寄存器内容,在返回前恢复寄存器内容。例如:子程序名:DIV8入口参数:AX中为16位有符号数出口参数:AX中为入口参数的八分之一注
6、:CX不改变DIV8:PUSH CX MOV CL,3 SAR AX,CL POP CX RET 如果需要保护的寄存器数量多,应该遵循”后进先出“的原则:如:PUSH AX PUSH BX PUSH SI .POP SI POP BX POP AX RET 子程序 CALL 指令有段间调用和段内调用之分,RET指令也有段间返回和段内返回之分,但是返回指令形式都是RET。那么,一个子程序的RET指令到底是段间返回还是段内返回呢?8086汇编语言采用过程定义过程定义来说明这个问题。8086汇编语言中,可以把子程序定义为过程过程,并使用伪指令NEAR指明是近近过程过程或用FAR指明是远过程远过程。一
7、个过程可以被其他程序所调用。对近过程近过程的调用和返回,汇编程序按近调用和近返回近调用和近返回处理;对远过程远过程的调用和返回,汇编程序按远调用和远返回远调用和远返回处理。定义过程的伪指令有 PROC、ENDP、NEAR和FAR 定义过程的伪指令PROC和ENOP总是成对出现的,这两条伪指令中间的内容作为一个过程,即一个子程序子程序。6.3 过程定义6.3.1、近过程定义一般形式如下:SEGMENT ASSUME 语句START:.CALL .HLT PROCNEAR .RET ENDP ENDS ENDSTART 在同一个代码段中,如果不定义过程,直接使用子程序,则汇编程序按照近调用和近返回
8、处理。主程序过程定义代码段定义6.3.2、远过程定义一般形式如下:SEGMENT ASSUME 语句START:.CALL .HLT ENDS SEGMENT ASSUME 语句 PROCFAR .RET ENDP ENDS ENDSTART 主程序过程定义代码段定义另一个代码段定义 子程序的参数传递包括入口参数入口参数传递和出口参数出口参数传递。参数传递的方法有下面几种:寄存器传递参数 内存单元传递参数 堆栈传递参数6.4.1、寄存器传递参数 寄存器传递参数,例如前面讲过的DIV4,用AX传递入口参数和出口参数。因为寄存器的数量有限,这种方法只适合参数很少的子程序。6.4.2、内存单元传递参
9、数 主程序在内存的数据段建立一个参数表,在调用子程序时,把参数表的首地址通过指针传递给子程序。如:6.4子程序的参数传递例:在数据串STR1中有20个字数据,数据串STR2中有5个字数据,编程。在STR1中查找子串STR2,找到则把BL置为1,否则把BL置为0。先编一个比较字数据串的子程序CMPSTR。子程序名:CMPSTR功能:把STR2与SI指到的字数据串比较(只比较前5个元素),相同则返回ZF1,否则返回ZF0。入口参数:SI指到一个数据串。出口参数:两个串相同则返回ZF1,否则返回ZF0程序:CMPSTR:LEA DI,STR2 MOV CX,5 CLD REPZ CMPSW RET
10、主程序段:MOV AX,DATA MOV DS,AX MOV ES,AX LEA DX,STR1 MOV CX,16 ;最多比较205116次 MOV BL,1SLP1:MOV SI,DX PUSH CX CALL CMPSTR POP CX JZ SLE1 ;如果STR2是STR1的子串,则ZF1。ADD DX,2 LOOP SLP1 DEC BLSLE1:HLT 6.4.3、堆栈传递参数 子程序可以利用堆栈传递参数。例如C语言中,函数的参数传递就是利用堆栈。首先要了解堆栈的构造和工作原理:堆栈段使用段寄存器SS。在CALL指令、RET指令、PUSH指令、POP指令中,按照“后进先出”的原则
11、工作,并使用SP寄存器内容为堆栈顶偏移量指令。使用BP为指针存取数据,默认的段寄存器也是SS,因此,BP也常常用来作存取堆栈中数据的偏移量指针。堆栈顶是变化的,随着压栈操作,堆栈顶向低地址方向生长压栈操作,堆栈顶向低地址方向生长。对堆栈的压栈操作和弹出堆栈操作必须平衡。利用堆栈传递子程序参数的方法是:主程序把参数压入堆栈,子程序从堆栈中取得参数,子程序返回时要使堆栈达到平衡。教材P206 例6.4:在数据段 ARY 数组中 有100个 16位数据,数组元素的个数放在COUNT单元中。对数组元素求和,和数为16位,和数存放到SUM单元。要求用堆栈传递ARY首地址及COUNT单元地址及SUM单元地
12、址。主程序段:MOV AX,DATA MOV DS,AX LEA AX,ARY PUSH AX ;ARY 首地址压入堆栈 LEA AX,COUNT PUSH AX ;COUNT 地址压入堆栈 LEA AX,SUM PUSH AX ;SUM 地址压入堆栈 CALL PROADD HLT ;后续程序下面分析一下程序流程进入PROADD子程序前后堆栈的情况:最初状态:三次AX压栈后:CALL 指令执行后:SP低地址高地址SPARY 地址SUM 地址COUNT 地址SPARY 地址SUM 地址COUNT 地址调用指令下面一条指令的 地址即子程序返回的地址。子程序:通常使用BP指针存取堆栈中的数据PRO
13、ADD PROC NEAR MOV BP,SP MOV BX,BP+6 ;取ARY首地址 MOV C X,BP+4 ;取数据个数 SUB AX,AX LP:ADD AX,BX INC BX INC BX LOOP LP MOV SI,BP+2 ;取SUM地址 MOV SI,AX ;存和数 RET 6 ;返回,并且使SPSP+6PROADD ENDP子程序中BP为指针存取数据的情况:BP+6BP+4BP+2BPARY 地址SUM 地址COUNT 地址调用指令下面一条指令的 地址,即子程序返回的地址。高地址低地址RET指令取返回地址,SP指针再加6,返回主程SP指针加2后的情况:序后的情况:SPA
14、RY 地址SUM 地址COUNT 地址SP高地址低地址问题:取堆栈中的数据能否使用BX、SI、DI作指针?答:可以,但是必须用段超越。如:PROADD PROC NEAR MOV SI,SP MOV BX,SS:SI+6 ;取ARY首地址 MOV C X,SS:SI+4 ;取数据个数 SUB AX,AX LP:ADD AX,BX INC BX INC BX LOOP LP MOV BX,SS:SI+2 ;取SUM地址 MOV BX,AX ;存和数 RET 6 ;返回,并且使SPSP+6PROADD ENDP 子程序的6.5子程序举例1、多位BCD码转换为无符号数4字节压缩的BCD码转换为4字节
15、无符号数。如:83 17 40 09H 转换为 04 F5 22 79H例:数据区D_BUF存有4字节压缩的BCD码,把它转换为4字节无符号数存放到B_BUF区。先编写一个把BCD码最高位移到AL的低位的子程序D_LEFT。例如:D_BUF 区 内容为 83 17 40 09H,则D_LEFT执行1次后,AL08H,D_BUF区内容为 31 74 00 90H。子程序名:D_LEFT入口参数:SI指到D_BUF 区首地址。出口参数:AL中为D_BUF区最高字节BCD码的高位,D_BUF区内容左移4位 子程序名:D_LEFT入口参数:SI 指到D_BUF 区首地址。出口参数:AL中为D_BUF区
16、最高字节BCD码的高位,D_BUF区内容左移4位,CX不变。D_LEFT:PUSH CX XOR AL,AL MOV CX,4 D_L1:SHL WORD PTR SI,1 RCL WORD PTR SI2,1 RCL AL,1 LOOP D_L1 POP CX RET 第二个子程序实现把B_BUF区内容乘10,乘积仍然放B_BUF区。子程序名:B_MUL入口参数:SI 指到B_BUF 区首地址,AL中为其中一个因子。另一个在B_BUF区。出口参数:乘积在B_BUF区,CX不变。B_MUL:PUSH CX MOV CX,4 MOV BL,0 CLC PUSHFB_M1:mov al,10 MU
17、L BYTE PTR SI ;SI*10-AX POPF ADC al,bl ;部分积的低位带进位加上次的部分积高位 ADC ah,0 ;低位相加的进位加 到高位上 mov SI,al mov bl,ah PUSHF inc SI LOOP B_M1 POPF POP CX RET 第三个子程序实现把B_BUF区内容加AL内容,和数仍然放B_BUF区。子程序名:B_ADD入口参数:SI 指到B_BUF 区首地址,AL中为其中一个加数。另一个在B_BUF区。出口参数:B_BUF区中为得到的和,CX不变。B_ADD:MOV AH,0 ADD SI,AX ADC WORD PTR SI+2,0 RET 完整的程序:DATA SEGMENTD_BUF DB 09H,40H,17H,83H B_BUF DB 4 DUP(0)DATA ENDSCODE SEGMENT ASSUME CS:CODE,DS:DATASTART:MOV AX,DATA MOV DS,AX MOV CX,8 D_B1:LEA SI,B_BUF CALL B_MUL LEA SI,D_BUF CALL D_LEFT LEA SI,B_BUF CALL B_ADD LOOP D_B1 HLTCODE ENDS END START