子程序存储在存储器中, 可供一个或多个调用程序 (主程序) 反复调用 主程序调用子程序时使用 CALL 指令,由子程序返回主程序时使用 RET 指令由于调用程序和子程序可以在同一个代码段中,也可以在不同的代码段中,因此, CALL 指令和 RET 指令也有近调用、近返回及远调用、远返回两类格式⑴ CALL NEAR PTR 近调用( near call)近调用是 CALL 指令的缺省格式,可以写为 "CALL rotine" 它调用同一个代码段内的子程序(子过程) ,因此,在调用过程中不用改变 CS 的值,只需将子程序的地址存入 IP 寄存器 CALL 指令中的调用地址可以用直接和间接两种寻址方式表示⑵ CALL FAR PTR 远调用( far call)远调用适用于调用程序 (也称为主程序) 和子程序不在同一段中的情况, 所以也叫做段间调用和近调用指令一样,远调用指令中的寻址方式也可用直接方式和间接方式⑶ RET 返回指令( return )RET 指令执行的操作是把保存在堆栈中的返回地址出栈,以完成从子程序返回到调用程序的功能● CALL 段内直接调用执行操作:① (SP) ← (SP)-2, ((SP)) ← (IP)当前② (IP) ← (IP)当前+ 16 位位移量(在指令的第 2、 3 个字节中)● CALL DESTIN 段内间接调用执行操作:① (SP) ← (SP)-2, ((SP)) ← (IP)当前② (IP) ← (EA) ; (EA)为指令寻址方式所确定的有效地址● CALL FAR PTR 段间直接调用执行操作:① (SP) ← (SP)-2, ((SP)) ← (CS)当前(SP) ← (SP)-2, ((SP)) ← (IP)当前② (IP) ← 偏移地址(在指令的第 2、 3 个字节中)(CS) ← 段地址(在指令的第 4、 5 个字节中)● CALL WORD PTR DESTIN 段间间接调用执行操作:① (SP) ← (SP)-2, ((SP)) ← (CS)当前(SP) ← (SP)-2, ((SP)) ← (IP)当前② (IP) ← (EA) ; (EA)为指令寻址方式所确定的有效地址(CS) ← (EA+2)从 CALL 指令执行的操作可以看出,第一步是把子程序返回调用程序的地址保存在堆栈中。
对段内调用,只需将 IP 的当前值,即 CALL 指令的下一条指令的地址存入 SP 所指示的堆栈字单元中对段间调用,保存返回地址则意味着要将 CS 和 IP 的当前值分别存入堆栈的两个字单元中CALL 指令的第二步操作是转子程序,即把子程序的入口地址交给 IP(段内调用)或CS:IP(段间调用) 对段内直接方式,调转的位移量,即子程序的入口地址和返回地址之间的差值就在机器指令的 2、 3 字节中对段间直接方式,子程序的偏移地址和段地址就在操作码之后的两个字中 对间接方式, 子程序的入口地址就从寻址方式所确定的有效地址中获得● RET 段内返回(近返回)执行操作: (IP) ← ((SP)), (SP) ← (SP)+ 2 ● RET 段间返回(远返回)执行操作: (IP) ← ((SP)), (SP) ← (SP)+ 2 (CS) ← ((SP)), (SP) ← (SP)+ 2 ● RET N 带立即数返回执行操作:① 返回地址出栈(操作同段内或段间返回)② 修改堆栈指针: (SP) ← (SP)+ N 子程序的最后一条指令必须是 RET 指令,以返回到主程序如果是段内返回,只需把保存在堆栈中的偏移地址出栈存入 IP 即可,如果是段间返回,则要把偏移地址和段地址都从堆栈中取出送到 IP 和 CS 寄存器中。
带立即数返回指令, 除完成偏移地址出栈或偏移地址和段地址出栈的操作外, 还要再使SP 的内容加上一个立即数 N,使堆栈指针 SP 移动到新的位置指令中的 N 可以是一个常数,也可以是一个表达式带立即数返回指令适用于 C 或 PASCAL 的调用规则,这些规则在调用过程 (子程序) 前先把参数压入堆栈, 子程序使用这些参数后, 如果在返回时丢弃这些已无用的参数,就在 RET 指令中包含一个数字,它表示压入到堆栈中参数的字节数,这样堆栈指针就恢复到参数入栈前的值CALL 指令和 RET 指令都不影响条件码例 3.43 根据下面调用程序和子程序的程序清单, 画出 RET 指令执行前和执行后的堆栈情况假设初始的 SS:SP=A000:10000000 B8 001E MOV AX,30 0003 BB 0028 MOV BX,40 0006 50 PUSH AX ; push data1 into stack 0007 53 PUSH BX ; push data2 into stack 0008 E8 0066 CALL ADDM ; call 000B B4 02 MOV AH,2 … … …0071 ADDM PROC NEAR ; entry point (IP) ← 0071=000b+00660071 55 PUSH BP ; save BP 0072 8B E4 MOV BP,SP ; addressing the stack with BP 0074 8B 46 04 MOV AX,[BP+4] ; get data2 from stack 0077 03 46 06 ADD AX,[BP+6] ; add data1 007A CD POP BP ; get back BP 007B C2 0004 RET 4 ; return and revert SP 007E ADDM ENDP 图 3.12 CALL 指令和 RET 指令对堆栈的影响如图 3.12 所示,主程序中的两条 PUSH 指令将数据 30 和 40 压入堆栈, CALL 指令执行后, 返回地址 000B 又压入堆栈, 紧接着程序控制转移到子程序 ADDM 。
子程序中的 PUSH指令又使 BP 的值进栈,此时 SP 指向栈顶 0FFA MOV 指令将 0FFA 传送给 BP,使 BP 作为寻址堆栈数据的指针 ( BP+4)指向的是 40, ( BP+6)指向的是 30,取出数据后用 POP指令恢复了 BP 原先的值,此时, (SP)=0FFC ,这是 RET 4 指令执行前的堆栈状态执行 RET 4 指令时,先使返回地址出栈: (IP) ← 000B, (SP)← 0FFC+2=0FFD ,然后,(SP)+4=0FFD+4=1000 ,结果使 SP 跳过了堆栈数据而回到了原始位置6.4.4 子程序应用举例【例】将一个给定的二进制数按位转换成相应的 ASCII 码字符串,送到指定的存储单元并显示如二进制数 10010011 转换成字符串为 ‘ 10010011’要求将转换过程写成子程序,且子程序应具有较好的通用性,而必须能实现对 8 倍和 16 倍二进制数的转换入口参数: DX 存放待转换的二进制数CX 存放待转换数的位数( 8 位或 16 位)DI 存放 ASCII 码首地址出口参数:转换后的字符串存放在以 DI 作指针的字节存贮区中程序如下:DATA SEGMENT NUM8 DB 93H NUM16 DW 0ABCDH ASCBUF DB 20 DUP( 0)DATA ENDS CODE SEGMENT ASSUME DS: DATA , CS: CODE , SS: STACK START: MOV AX , DATA MOV DS, AX MOV DX , 0 MOV DL , NUM8 ;转换二进制数送 DX MOV CX , 8 ;置位数 8 LEA DI, ASCBUF ; 字符串首址→ DI CALL BTASC ;调用子程序 BTASC MOV [DI] , BYTE PTR 0DH MOV [DI+1] , BYTE PTR 0AH MOV [DI+2] , BYTE PTR ‘ $’LEA DX , ASCBUF MOV AH , 9 INT 21H MOV DX , NUM16 MOV CX , 16 ;置位数 16 LEA DI, ASCBUF CALL BTASC MOV [DL] , BYTE PTR 0DH MOV [DL+1] , BYTE PTR 0AH MOV [DL+2] , BYTE PTR ‘ $ ’ ; 显示转换后的字符串LEA DX , ASCBUF MOV AH , 9 INT 21H BTASC PROC PUSH AX ;保存 AX MOV AL , 0 CMP CX , 8 ;比较 8 位数JNE L1 ;直接转换 16 位数MOV DH , DL ; 8 位数转换送 DH L1: ROL DX, , 1 ; DX 最高位移入 CF RCL AL, 1 ; CF 移入 AL 最低位ADD AL , 30H MOV [DI] , AL INC DI LOOP L1 POP AX RET BTASC ENDP CODE ENDS END START ( 1)通过寄存器传送参数这是最常用的一种方式,使用方便,但参数很多时不能使用这种方法。
十进制到十六进制数转换程序程序要求从键盘取得一个十进制数,然后把该数以十六进制形式在屏幕上显示出来采用子程序结构, 用一个子程序 DECIBIN 实现从键盘取得十进制数并把它转换为二进制数; 另一个子程序 BINIHEX 把此二进制数以十六进制数的形式在屏幕上显示出来为避免屏幕上的重叠,另外用 CRLF子程序取得回车和换行的效果整个程序结构如动画所示,在这里,各个子程序之间用 BX 寄存器来传送信息decihex segment assume cs:decihex ; 程序的主要部分main proc far repeat: call decibin ; 调用子程序 decibin call crlf ; 调用子程序 crlf call binihex ; 调用子程序 binihex call crlf jmp repeat main endp ; 子程序 decibin decibin proc near mov bx,0 newchar: mov ah,1 int 21h ; 从键盘接收单个字符; 非 0~ 9 之间的数退出sub al,30h jl exit cmp al,9d jg exit cbw ; al 扩展到 ax ; BX 中的数乘以 10 xchg ax,bx mov cx,10d mul cx xchg ax,bx ; 把 ax 加到 bx 中add bx,ax jmp newchar ; 接收下一个字符exit: ret decibin endp ; 子程序 binihexbinihex proc near mov ch,4 rotate: mov cl,4 rol bx,cl mov al,bl and al,0fh add al,30h cmp al,3ah jl printit add al,7h printit: mov dl,al mov ah,2 int 21h dec ch jnz rotate ret binihex endp ; 子程序 crlf crlf proc near ; 显示回车mov dl,0dh mov ah,2 int 21h ; 显示换行mov dl,0ah mov ah,2 int 21h ret crlf endpdecihex ends end。