子程序的调用和返回指令
子程序的调用和返回是一对互逆操作,也是一种特殊的转移操作。
一方面,之所以说是转移,是因为当调用一个子程序时,程序的执行顺序被改变,CPU将转而执行子程序中的指令序列,在这方面,调用子程序的操作含有转移指令的功能,子程序的返回指令的转移特性与此类似;
另一方面,转移指令是一种“一去不复返”的操作,而当子程序完后,还要求CPU能转而执行调用指令之下的指令,它是一种“有去有回”的操作。
为了满足子程序调用和返回操作的特殊性,在指令系统中设置了相应的特定指令。
7.2.1 调用指令(CALL)
调用子程序指令的格式如下:
CALL 子程序名/Reg/Mem
子程序的调用指令分为近(near)调用和远(far)调用。如果被调用子程序的属性是近的,那么,CALL指令将产生一个近调用,它把该指令之后地址的偏移量(用一个字来表示的)压栈,把被调用子程序入口地址的偏移量送给指令指针寄存器IP即可实现执行程序的转移。近调用指令的堆栈操作如图7.1所示。
如果被调用子程序的属性是远的,那么,CALL指令将产生一个远调用。这时,调用指令不仅要把该指令之后地址的偏移量压进栈,而且也要把段寄存器CS的值压进栈。在此之后,再把被调用子程序入口地址的偏移量和段值分别送给IP和CS,这样完成了子程序的远调用操作。远调用指令的堆栈操作如图7.2所示。
子程序调用指令本身的执行不影响任何标志位,但子程序体中指令的执行会改变标志位,所以,如果希望子程序的执行不能改变调用指令前后的标志位,那么,就要在子程序的开始处保护标志位,在子程序的返回前恢复标志位。
例如: |
|
CALL DISPLAY |
;DISPLAY是子程序名 |
CALL BX |
;BX的内容是子程序的偏移量 |
CALL WORD1 |
;WORD1是内存字变量,其值是子程序的偏移量 |
CALL DWORD1 |
;DWORD1是双字变量,其值是子程序的偏移量和段值 |
CALL word ptr [BX] |
;BX所指内存字单元的值是子程序的偏移量 |
CALL dword ptr [BX] |
;BX所指内存双字单元的值是子程序的偏移量和段值 |
7.2.2 返回指令(RET)
当子程序执行完时,需要返回到调用它的程序之中。为实现此功能,指令系统提供了一条专用的返回指令。其格式如下:
RET/RETN/RETF [Imm]
子程序的返回在功能上是子程序调用的逆操作。为了与子程序的远、近调用相对应,子程序的返回也分:远返回和近返回。返回指令在堆栈操作方面是调用指令的逆过程(如图7.3所示)。其具体规定如下:
、在近类型的子程序中,返回指令RET是近返回,其功能是把栈顶之值弹出到指令指针寄存器IP中,SP会被加2(如图7.3所示); |
、在远类型的子程序中,返回指令RET是远返回,其功能是:先弹出栈顶之值到IP中,再弹出栈顶之值到CS之中,SP总共会被加4(如图7.4所示)。 |
如果返回指令后面带有立即数(其值通常为偶数),则表示在得到返回地址之后,SP还要增加的偏移量,它不是类似于高级语言中子程序的返回值(如图7.5所示)。
在MASM 5.0及其以后版本中,可用指令RETN或RETF来显式地告诉汇编程序是本子程序的返回是近返回,还是远返回。
例如: |
|
RET |
;可能是近返回,也可能是远返回 |
RETN |
;近返回指令 |
RETF |
;远返回指令 |
RET 6 |
;子程序返回后,(SP)←(SP) 6 |
例7.1 编写一个子程序UPPER,实现把寄存器AL中存放的字符变大写。
解: |
||||
|
;子程序功能:把AL中存放的字符变大写 |
|||
;入口参数:AL |
||||
;出口参数:AL |
||||
;算法描述:判断AL中字符必须在'a'~'z'之间才能把该字符变为大写 |
||||
UPPER |
PROC |
|||
CMP |
AL, 'a' |
;书写'a'的ASCII码61H也可以 |
||
JB |
over |
|||
CMP |
AL, 'z' |
|||
JA |
over |
|||
SUB |
AL, 20H |
;书写指令AND AL, 0DFH也可以 |
||
over: |
RET |
|||
UPPER |
ENDP |
例7.2 编写一个求字符串长度的子程序StrLen,该字符串以0为结束标志,其首地址存放在DS:DX,其长度保存在CX中返回。
解: |
||||
|
;子程序功能:求字符串的长度 |
|||
;入口参数:DS:DX存放字符串的首地址,该字符串以0为结束标志 |
||||
;出口参数:CX存放该字符串的长度 |
||||
;算法描述:用BX来指针来扫描字符串中的字符,如果遇到其结束标志,则停止扫描字符串操作 |
||||
StrLen |
PROC |
|||
PUSH |
AX |
|||
PUSH |
BX |
;用堆栈来保存子程序所用到的寄存器内容 |
||
XOR |
CX, CX |
|||
XOR |
AL, AL |
|||
MOV |
BX, DX |
|||
again: |
CMP |
[BX], AL |
||
JZ |
over |
|||
INC |
CX |
;增加字符串的长度 |
||
INC |
BX |
;访问字符串的指针向后移 |
||
JMP |
again |
|||
over: |
POP |
BX |
;恢复在子程序开始时所保存的寄存器内容 |
|
POP |
AX |
|||
RET |
||||
StrLen |
ENDP |
7.3 子程序的参数传递
子程序一般都是完成某种特定功能的程序段。当一个程序调用一个子程序时,通常都向子程序传递若干个数据让它来处理;当子程序处理完后,一般也向调用它的程序传递处理结果,我们称这种在调用程序和子程序之间的信息传递为参数传递。
用程序向子程序传递的参数称为子程序的入口参数,子程序向调用它的程序传递的参数称为子程序的出口参数。子程序的入口参数和出口参数都是任意项,对某个具体的子程序来说,要根据具体情况来确定其入口和出口参数,也可以二者都没有。
程序和被调用子程 序之间的参数传递方法是程序员自己或和别人事先约定好的信息传递方法。这种信息传递方法可以是多种多样的,在本节,我们只介绍常用的、行之有效的参数传递 方法有:寄存器传递参数、约定存储单元传递参数和堆栈传递参数等。如果对其它的参数传递方法感兴趣的话,可参考其它《汇编语言程序设计》书籍。
7.3.1 寄存器传递参数
一方面,由于 CPU中的寄存器在任何程序中都是“可见”的,一个程序对某寄存器赋值后,在另一个程序中就能直接使用,所以,用寄存器来传递参数最直接、简便,也是最常 用的参数传递方式。但另一方面,CPU中寄存器的个数和容量都是非常有限,所以,该方法适用于传递较少的参数信息。
例7.1是用寄存器传递参数的例子,子程序处理的数据被保存在寄存器AL中。假设有下列的程序段:
例7.3 按五位十进制的形式显示寄存器BX中的内容,如果BX的值小于0,则应在显示数值之前显示负号'-'。
例如:(BX)=123,显示:00123;(BX)=-234,显示:-00234;
|
… |
||
MOV |
AL, 'b' |
||
CALL |
UPPER |
;子返回时,(AL)='B' |
|
… |
|||
MOV |
AL, '2' |
||
CALL |
UPPER |
;子返回时,AL的值不变,因为'2'不是字母 |
|
… |
解: |
||||
|
;子程序功能:把寄存器BX的内容按十进制有符号数显示出来 |
|||
;入口参数:BX |
||||
;出口参数:无,只有显示信息 |
||||
;算法描述: |
1、定义6个字节的存储单元 2、先判断BX是否小于零,如果是,则先显示负号'-',再取BX的绝对值; 3、采用除10,得余数的方法,从低位向高位求出每位十进制位; 4、输出数据的字符串。 |
|||
SubData |
SEGMENT |
|||
DB |
5 DUP('0'), 0ah, 0dh, '$' ;0ah、0dh:换行、回车 |
|||
SubData |
ENDS |
|||
DISPBX |
PROC |
|||
ASSUME |
DS:SubData |
|||
PUSH |
DS |
|||
PUSH |
DX |
|||
PUSH |
CX |
|||
PUSH |
AX |
|||
MOV |
AX, SubData |
;取子程序所用的数据区段地址 |
||
MOV |
DS, AX |
|||
CMP |
BX, 0 |
|||
JGE |
next |
|||
MOV |
DL, '-' |
|||
MOV |
AH, 2 |
|||
INT |
21H |
;显示负号'-' |
||
NEG |
BX |
;求-BX,使其值为正数 |
||
next: |
MOV |
SI, 4 |
||
MOV |
AX, BX |
|||
MOV |
CX, 10D |
|||
again: |
XOR |
DX, DX |
||
IDIV |
CX |
;DX存放余数,AX存放商 |
||
ADD |
DL, '0' |
|||
MOV |
[SI], DL |
|||
DEC |
SI |
|||
JGE |
again |
|||
XOR |
DX, DX |
|||
MOV |
AH, 9 |
|||
INT |
21H |
;调用中断21的功能9,显示DS:DX指向的字符串 |
||
POP |
AX |
|||
POP |
CX |
|||
POP |
DX |
|||
POP |
DS |
|||
RET |
||||
DISPBX |
ENDP |