当前位置:文档之家› 子程序调用指令

子程序调用指令

子程序存储在存储器中,可供一个或多个调用程序(主程序)反复调用。主程序调用子程序时使用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:1000。

0000 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+0066

0071 55 PUSH BP ; save BP

0072 8B E4 MOV BP,SP ; addressing the stack with BP

0074 8B 46 04MOV 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 0004RET 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作指针的字节存贮区中

程序如下:

DA TA SEGMENT

NUM8 DB 93H

NUM16 DW 0ABCDH

ASCBUF DB 20 DUP(0)

DA TA ENDS

CODE SEGMENT

ASSUME DS:DA TA,CS:CODE,SS:STACK

START:MOV AX,DA TA

MOV DS,AX

MOV DX,0

MOV DL,NUM8 ;转换二进制数送DX

MOV CX,8 ;置位数8

LEA D I,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 D I,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 A L,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

; 子程序

binihex 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 endp

decihex ends

end main

我们已经知道,一个子程序也可以作为调用程序去调用另一个子程序,这种情况称为子程序的嵌套。嵌套的层次不限,其层数称为嵌套深度。动画表示了嵌套深度为2时的子程序嵌套情况。

嵌套子程序的设计并没有什么特殊要求,除子程序的调用和返回应正确使用CALL和RET指令外,要注意寄存器的保存和恢复,以避免各层次子程序之间因寄存器冲突而出错的情况发生。如果程序中使用了堆栈,例如使用堆栈来传送参数等,则对堆栈的操作要格外小心,避免发生因堆栈使用中的问题而造成子程序不能正确返回的错误。

相关主题
文本预览
相关文档 最新文档