1、第6章子程序设计及系统调用 调用程序与子程序 调用与返回指令 子程序设计,6.1 调用程序与子程序,子程序:在许多应用程序中,常常需要多次使用某功能的指令序列。这时,为了减少重复编写程序,节省内存空间,把这一功能的指令序列组成一个相对独立的程序段。在程序运行时,如果需要使用这个给定的功能,就转移到这个独立的程序段,待这个独立的程序段指令序列执行完后,又返回到原来位置继续运行程序。我们把这个相对独立的程序段就叫子程序或过程。 调用程序:编制程序时,按需要转向子程序,称为子程序调用,或称为过程调用。调用子程序的程序称为调用程序或主程序。主、子程序是相对而言的。但子程序一定是受调用程序或主程序调用的
2、。,返回,6.2 调用与返回指令1过程调用指令CALL 指令格式: CALL DST其中DST为过程的目标地址。指令功能: 把CALL指令的下一条指令地址(称为返回点或断点) 推入堆栈保存,然后转到目标地址(DST)。CALL指令可以在段内、段间调用,寻址方式分为直接和间接两种。 (1)段内直接调用 指令中DST给出转向地址。首先将指令指针IP推入堆栈保存,然后把从指令中得到的距目标过程相对偏移量(最大为32K字节)加到指令指针IP上(得到子程序的入口地址),实现过程调用。 执行的操作为:(SP)(SP)2(SP)+1,(SP)(IP)(IP)(IP)+D16 其中D16为机器指令的位移量,它
3、是转向地址和返回地址之间的差直。,(2)段内间接调用 执行的操作为:(SP)(SP)2(SP)+1,(SP)(IP)(IP)(EA)其中EA 是由DST的寻址方式所确定的有效地址。 (3)段间直接调用首先把现行的代码段寄存器CS的内容和指令指针IP的值入栈保存,然后把指令中的地址偏移字和段地址字送入IP和CS。 执行的操作为:(SP)(SP)2(SP)+1,(SP)(CS)(SP)(SP)2(SP)+1,(SP)(IP)(IP)偏移地址(指令的第2、3个字节)(CS)段地址(指令的第4、5个字节),(4)段间间接调用首先把现行的代码段寄存器CS的内容和指令指针IP的值入栈保存,然后把指令中的地
4、址偏移字和段地址字送入IP和CS。 执行的操作为:(SP)(SP)2(SP)+1,(SP)(CS)(SP)(SP)2(SP)+1,(SP)(IP)(IP)(EA)(CS)(EA+2) 其中EA 是由DST的寻址方式所确定的有效地址。 2返回指令RET 指令格式:RET 指令功能:RET指令通常写在一个子程序(或过程)的最后,用以返回到调用这个子程序的断点处。,RET指令也属于无条件转移指令。可以在段内或段间返回。 (1)段内返回RET指令由堆栈弹回断点偏移量到指令指针IP,实现段内调用返回。 执行的操作为: (IP)(SP)+1,(SP) (SP)(SP)+2 (2)段间返回RET指令除由堆栈
5、弹回断点偏移量到指令指针IP外,还由堆栈弹回断点所在段基址到代码段寄存器CS,实现段间调用返回。 执行的操作为:(IP)(SP)+1,(SP)(SP)(SP)+2(CS)(SP)+1,(SP)(SP)(SP)+2,返回,6.3 子程序设计 6.3.1 子程序定义 6.3.2 子程序的调用与返回 6.3.3 现场保护与恢复 6.3.4 参数的传递方式 6.3.5 子程序调用举例 6.3.6 子程序的嵌套与递归,返回,6.3.1 子程序定义 格式: 过程名 PROC NEAR/FAR过程名 ENDP 其中PROC表示过程定义开始,ENDP表示过程定义结束。一般过程名同标号一样,具有三种属性,即段属
6、性、偏移地址属性以及类型属性。而类型属性可指定为NEAR或FAR两种类型。具有NEAR属性的子程序与调用程序应在同一个逻辑段中,而具有FAR属性的子程序和调用程序不在同一个逻辑段内。若为NEAR类型属性时可以省略“NEAR”。 如下面的定义皆为正确的过程定义: 段内调用:A PROC NEAR A PROC或A ENDP A ENDP,段间调用: B PROC FAR B ENDP 6.3.2 子程序的调用与返回 1段内调用 前面已经讲过,子程序调用可以在段内调用,也可以在段间调用。如果是段内调用,则在过程定义时,必须定义为NEAR类型。这时,过程定义可放在代码段中,置于主程序体之前或之后。
7、【例6.1】已知三个八位无符号数X、Y、Z,分别存放于BUF、BUF+1和BUF+2存储单元,计算2X+5Y+8Z,结果送RES和RES+1单元。,返回,NAME EXAM6_1 DATA SEGMENT BUF DB 71H,0A4H,9BH RES DB 2 DUP(?) DATA ENDS STACK SEGMENT PARA STACK STACK STAPN DB 100 DUP(?) TOP EQU LENGTH STAPN STACK ENDS CODE SEGMENTASSUME CS:CODE, DS:DATA, SS:STACK START:MOV AX,DATAMOV D
8、S,AXMOV AX,STACKMOV SS,AXMOV AX,TOPMOV SP,AXMOV AX,0 ;AX清0MOV WORD PTR RES,AX ;RES字单元清0,LEA BX,BUF ;置地址指针MOV AL,2CALL MULL ;过程调用MOV AL,5CALL MULL ;过程调用MOV AL,8CALL MULL ;过程调用MOV AH,4CHINT 21H MULL PROC ;乘法子程序MUL BYTE PTR BX ;做乘法结果在AXADD WORD PTR RES,AX ;做加法MOV AX,0 ;AX清0 INC BX ;地址加1 RET ;返回主程序 MULL
9、 ENDP CODE ENDS END START,2段间调用 子程序如果段间调用时,必须定义为FAR类型。 段间调用通常用于不同模块之间的调用。 编写不同模块的段间调用程序,应该注意以下几个个问题:(1)主程序模块和子程序模块分别汇编,然后用连接程序将它们连接在一起。(2)在主程序模块中,主程序所调用的外部过程名必须用EXTRN伪指令说明。(3)在过程模块中,提供给外段调用的过程名必须用PUBLIC伪指令说明。(4)模块间其它公用符号名及外部符号名的定义不可缺少。 【例6.2】将【例6.1】 中的段内调用改为段间调用,源程序为: NAME EXAM6_2 EXTRN MULL:FAR ;外部
10、引用说明 PUBLIC RES ;定义公用名 DATA SEGMENT BUF DB 71H,0A4H,9BH RES DB 2 DUP(?) DATA ENDS STACK SEGMENT PARA STACK STACK STAPN DB 100 DUP(?) TOP EQU LENGTH STAPN STACK ENDS CODE SEGMENTASSUME CS:CODE, DS:DATA, SS:STACK START:MOV AX,DATAMOV DS,AXMOV AX,STACKMOV SS,AXMOV AX,TOPMOV SP,AXMOV AX,0 ;AX清0MOV WORD
11、PTR RES,AX ;RES字单元清0LEA BX,BUF ;置地址指针,MOV AL,2CALL MULL ;过程调用MOV AL,5CALL MULL ;过程调用MOV AL,8CALL MULL ;过程调用MOV AH,4CHINT 21H CODE ENDSEND STARTNAME L6_2AEXTRN RES:BYTE ;外部引用说明 CODEA SEGMENTMULL PROC FAR ;乘法子程序ASSUME CS:CODEA PUBLIN MULL ;定义公用名MUL BYTE PTR BX ;做乘法结果在AXADD WORD PTR RES,AX ;做加法MOV AX,0
12、 ;AX清0 INC BX ;地址加1 RET ;返回主程序 MULL ENDP CODEA ENDSEND,返回,6.3.3 现场保护与恢复 要保护的寄存器:应该是在子程序中将被使用,返回调用程序后仍然需要使用其原有内容的那些寄存器。即保护调用程序和子程序两者在使用上发生冲突的那些寄存器。但在编程时,一时很难弄清哪些是有冲突的寄存器,一种较为简单的方法是把所有的寄存器均加以保护。 一般在子程序中进行寄存器保护较好。即在子程序的开始部分,先进行相关寄存器(主要是在子程序中使用的各寄存器)的保护。然后再进行子程序的处理操作。在执行完子程序后,返回前,先恢复各寄存器内容后,再返回调用程序。例如:
13、SUBT PROC NEARPUSH AXPUSH BXPUSH CXPUSH DXPOP DXPOP CXPOP BXPOP AXRET SUBT ENDP,返回,6.3.4 参数的传递方式 参数传递通常有三种方法:寄存器、存储器和堆栈分别作为传递的工具 。 1利用寄存器传递参数实现的方法是把子程序所需要的入口参数,由调用程序予先放入指定的寄存器中。在进入子程序后,子程序就可直接对这些寄存器内容进行操作了。同样子程序的运行结果,也可置入寄存器中,把它们作为子程序的出口参数寄存器使用。【例6.3】以BCDBUF为首址的内存缓冲区存放着若干单元的用BCD码表示的十进制数。每个单元中放两位BCD码
14、,要求把它们分别转换为ASCII码,存放在ASCBUF为首址的缓冲区中,且高4位BCD码转换成的ASCII码放在地址较高的单元。并且要求边转换边显示这些ASCII码。 源程序为: NAME EXAM6_3 DATA SEGMENT BCDUBF DB 71H,24H,96H,87H,12H,78H,56H,34H,63H,45H COUNT EQU $-BCDBUF ASCBUF DB 20 DUP(?) DATA ENDS,STACK SEGMENT PARA STACKSTACK STAPN DB 100 DUP(?) TOP EQU LENGTH STAPN STACK ENDS COD
15、E SEGMENTASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK START: MOV AX,DATAMOV DS,AXMOV ES,AXMOV AX,STACKMOV SS,AXMOV AX,TOPMOV SP,AX MOV SI,OFFSET BCDBUF ;BCD码首址MOV DI,OFFSET ASCBUF ;ASCII码首址MOV CX,COUNT ;组合BCD码个数CLD ;DF=0 LD: LODSB ;取一个组合BCD码MOV BL,AL ;保存AND AL,0FHOR AL,30H ;BCD码低位转换为ASCII码,MOV DL,AL ;存入D
16、LSTOSB ;存入ASCII码存储区 CALL DISP ;显示ASCII码字符MOV AL,BL ;BCD码送回ALPUSH CX ;保存计数MOV CL,4SHR AL,CLOR AL,30H ;BCD码高位转换为ASCII码MOV DL,AL ;存入DLSTOSB ;存入ASCII码存储区CALL DISP ;显示ASCII码POP CX ;弹出计数LOOP LP ;计数减1不为0继续MOV AH,4CHINT 21H;子程序名:DISP;功能:显示ASCII字符;入口参数:ASCII码在DL中,DISP PROCMOV AH,2 ;2号系统功能调用INT 21HMOV DL, MOV
17、 AH,2INT 21H RET DISP ENDP CODE ENDSEND START 2利用存储器传递参数利用存储器参数传递,适合于参数较多的情况。大多是在数据区建立参数表,里面放有子程序所要使用的参数。调用程序把该参数表首地址传送给子程序。子程序通过参数表取得所需参数,在数据处理完后,将结果也送到指定的数据储存区中。 【例6.4】 将例【例6.3】的程序改为用存储器传递参数。,源程序为: NAME EXAM6_4 DATA SEGMENT BCDUBF DB 71H,24H,96H,87H,12H,78H,56H,34H,63H,45H COUNT EQU $-BCDBUF ASCBU
18、F DB 20 DUP(?) DATA ENDS STACK SEGMENT PARA STACKSTACK STAPN DB 100 DUP(?) TOP EQU LENGTH STAPN STACK ENDS CODE SEGMENTASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK START: MOV AX,DATAMOV DS,AXMOV ES,AXMOV AX,STACKMOV SS,AXMOV AX,TOPMOV SP,AX MOV SI,OFFSET BCDBUF ;BCD码首址,MOV DI,OFFSET ASCBUF ;ASCII码首址MOV C
19、X,COUNT ;组合BCD码个数CLD ;DF=0 LP: LODSBMOV BL,ALAND AL,OFHOR AL,30HSTOSBCALL DISPMOV AL,BLPUSH CXMOV CL,4SHR AL,CLOR AL,30HSTOSBCALL DISPPOP CXLOOP LPMOV AH,4CHINT 21H,;子程序名:DISP;功能:显示ASCII字符;入口参数:DI指向ASCII码单元 DISP PROCPUSH DIDEC DI ;该DI是要显示字符所在单元地址MOV DL,DIMOV AH,2INT 21HPOP DIMOV DL, MOV AH,2INT 21HR
20、ET DISP ENDP CODE ENDSEND START,3利用堆栈传递参数利用堆栈进行参数传递,就是在主程序中将参数推入堆栈,而在子程序中将参数从堆栈中弹出。 用堆栈传递参数也适于多参数的情况,但要注意堆栈后进先出的特点,避免参数进出栈的混乱。 【例6.5】 将例【例6.3】的程序改为用堆栈传递参数。LP: LODSBMOV BL,ALAND AL,OFHOR AL,30HMOV AH,0PUSH AX ;保存低位BCD码对应的ASCII码 STOSBCALL DISPMOV AL,BLMOV DX,CX,MOV CL,4SHR AL,CLOR AL,30HMOV CX,DXMOV A
21、H,0PUSH AXSTOSBCALL DISPLOOP LP MOV AH,4CHINT 21H;子程序名:DISP;功能:显示ASCII字符 ;入口参数: ASCII在堆栈中 DISP PROCMOV BP,SPMOV DL,BP+2 ;取出ASCII码字符送入DLMOV AH,2INT 21HMOV DL, ,MOV AH,2INT 21HRET DISP ENDP CODE ENDSEND START请大家注意:前面所介绍的三种参数的传递方法,并不是固定不变的,即它们是可以综合使用的。依实现的需要和情况的不同,可以使用其中一种方式,也可以同时使用几种方式的混合。有的时候还可能并不需要参
22、数传递。,返回,6.3.5 子程序调用举例 【例6.6】将一个给定的二进制数按位转换成相应的ASCII码字符串,送到指定的存储单元显示。如二进制数10010011转换成字符串为10010011。要求将转换过程写成子程序,且子程序应具有较好的通用性,而必须能实现对8位和16位二进制数的转换。 入口参数:DX存放待转换的二进制数CX存放待转换数的位数(8位或16位)DX存放ASCII码首址 出口参数:转换后字符串放在以DI作指针的字节存储区中。 NAME EXAM6_6DATA SEGMENTNUM8 DB 93HNUM16 DW 0ABCDHASCBUF DB 20 DUP(0)DATA END
23、SSTACK SEGMENT STACKDB 200 DUP(0)STACK ENDSCODE SEGMENT,ASSUME DS:DATA, CS:CODE, SS:STACKSTART: MOV AX,DATAMOV DS,AXMOV DX,0MOV DL,NUM8 ;转换二进制数送DXMOV CX,8LEA DI,ASCBUF ;字符串首址DICALL BTASC ;调用子程序BTASCMOV DI,BYTE PTR 0DHMOV DI+1,BYTE PTR 0AHMOV DI+2,BYTE PTR $LEA DX,ASCBUFMOV AH,9INT 21HMOV DX,NUM16MOV
24、 CX,16 ;置位数16LEA DI,ASCBUFCALL BTASC,MOV DI,BYTE PTR 0DHMOV DI+1,BYTE PTR 0AHMOV DI+2,BYTE PTR $ ;显示转换后的字符串LEA DX,ASCBUFMOV AH,9INT 21HMOV AH,4CHINT 21HBTASC PROCPUSH AX ;保存AXMOV AL,0CMP CX,8 ;比较8位数JNE L1 ;直接转换16位数MOV DH,DL ;8位数转换送DHL1: ROL DX,1 ;DX最高位移入CFMOV AL,0RCL AL,1 ;CF移入AL最低位,ADD AL,30HMOV BY
25、TE PTR DI,ALINC DILOOP L1POP AXRETBTASC ENDPCODE ENDS END START【例6.7】已知某班N个学生的成绩,试编制一个子程序统计不及格,6069分,7079分,8089分,9099分及100分的人数,分别存放到以S为首址的单元中。 入口参数:以变量SCORE为首址的字存储单元的值。CX存储待处理的学生人。 出口参数:以S为首址的字存储区的值。,NAME EXAM6_7 STACK SEGMENT STACKDB 200 DUP (0) STACK ENDS DATA SEGMENT SCORE DW 78,89,83,54,35,76,74
26、,85,90,100,66,95 N EQU ($-SCORE)/2 S DW 6 DUP (0) CODE SEGMENTASSUME CS:CODE,DS:DATA,SS:STACK START: MOV AX,DATAMOV DS,AXMOV CX,NCALL COUNTMOV AH,4CHINT 21H COUNT PROCMOV SI,0 NEXT: MOV AX,SCORESICMP AX,60JB L1MOV BX,10DIV BL,CBWMOV BX,AXSUB BX,5SAL BX,1INC WORD PTR SBXJMP L2 L1: MOV BX,0SAL BX,1INC
27、 WORD PTR SBX L2: ADD SI,2LOOP NEXTRET COUNT ENDP CODE ENDSEND START【例6.8】将AX中的十六位有符号二进制数以十进制形式显示输出子程序F2T10。该程序首先判断AX中数的符号,若数为负数,则将负号“-”送入输出缓冲区,并求(AX)的绝对值;若AX中的数为正数,则不做其他处理,此时(AX)即为无符号二进制数。然后将无符号二进制数转换成十进制数,可采用将(AX)除以10,得到第一个商和第一个余数,第一个余数就是所求十进制数的个数;将第一个商除以10,得到第二个商和余数,第二余数就是所求十进制数的十位数,这一过程一直循环到商数为0
28、时,,得到的余数就是所求十进制数的最高位数,为了得到转换后的十进制数ASCII字符串,可利用堆栈的先进后出原则来实现。NAME EXAM6_8 DATA SEGMENT BINARY DW 7FFFH,0,0FFFEH,50H,8000H,1000H,2000HN=($-BINARY)/2 DATA ENDS STACK SEGMENT STACKDB 200 DUP (0) STACK ENDS CODE SEGMENTASSUME CS:CODE,DS:DATA,SS:STACK START: MOV AX,DATAMOV DS,AX MOV CX,NLEA DI,BINARY LOPA:
29、 MOV AX,DICALL F2T10MOV DL, ,MOV AH,2,INT 21HADD DI,2LOOP LOPAMOV AH,4CHINT 21H DATA SEGMENT BUF DB 7 DUP (?) DATA ENDS F2T10 PROCPUSH BXPUSH DXPUSH SIPUSH CXLEA SI,BUFOR AX,AX JNS PLUSNEG AXMOV SI,BYTE PTR -INC SI PLUS: MOV BX,10MOV CX,0 LOP1: MOV DX,0DIV BXPUSH DX,INC CXOR AX,AXJNE LOP1 LOP2: POP
30、AXCMP AL,10JB L1ADD AL,7 L1: ADD AL,30HMOV SI,ALINC SIDEC CXJNE LOP2MOV SI,BYTE PTR $LEA DX,BUFMOV AH,9INT 21HPOP CXPOP SIPOP DXPOP BXRET F2T10 ENDP CODE ENDSEND START,返回,6.3.6 子程序的嵌套与递归 1子程序的嵌套 一个程序可以调用一个或多个子程序。那么一个子程序是否也可以调用另一个子程序呢?回答是肯定的。这种一个子程序调用另一个子程序的情况,称为子程序的嵌套。,2子程序的递归调用子程序的递归调用是指一个子程序直接或间接地
31、调用自己。递归子程序一般对应于数学上对函数的递归定义,它往往能设计出效率较高的程序,完成相当复杂的计算,递归调用要注意必须有结束递归调用的判断语句。这里以阶层函数为例,说明递归子程序的设计方法。 STACK SEGMENT STACKDB 200 DUP(0) STACK ENDS DATA SEGMENT N DW 5 RESULT DW ? DATA ENDS CODE SEGMENTASSUME CS:CODE,SS:STACK,DS:DATA START: MOV AX,DATAMOV DS,AXMOV AX,NCALL FACTMOV AX,RESULTMOV AH,4CH,INT 21H FACT PROCCMP AX,0JNE L1MOV RESULT,1JMP EXIT L1: PUSH AXDEC AXCALL FACTPOP AXMUL RESULTMOV RESULT,AX EXIT: RET FACT ENDP CODE ENDSEND START,返回,