第7章 子程序和库
子程序是程序设计所常见的基本概念,汇编语言也提供了编写子程序的方法。
本章主要介绍子程序的定义、调用和返回、子程序的参数传递等知识。此后,还将讲解如何构造自己的子程序库。
7.1 子程序的定义
如果某程序段在源程序内反复出现,那么,就可把该程序段定义为子程序。这样可以缩短源程序长度、节省目标程序的存储空间,也可提高程序的可维护性和共享性。
定义子程序的一般格式如下: | |||
子程序名 | PROC [NEAR | FAR] | ||
… | ;子程序体 | ||
子程序名 | ENDP | ||
对子程序定义的具体规定如下:
、“子程序名”必须是一个合法的标识符,并前后二者要一致; | |
、PROC和ENDP必须是成对出现的关键字,它们分别表示子程序定义开始和结束; | |
、子程序的类型有近(NEAR)、远(FAR)之分,其缺省的类型是近类型; | |
、如果一个子程序要被另一段的程序调用,那么,其类型应定义为FAR,否则,其类型可以是NEAR。显然,NEAR类型的子程序只能被与其同段的程序所调用; | |
、子程序至少要有一条返回指令,也可有多条返回指令。返回指令是子程序的出口语句,但它不一定是子程序的最后一条语句; | |
、子程序名有三个属性:段值、偏移量和类型。其段值和偏移量对应于子程序的入口地址,其类型就是该子程序的类型。 | |
编写子程序除了要考虑实现子程序功能的方法外,还要养成书写子程序说明信息的好习惯。其说明信息一般包括以下几方面内容:
、功能描述 | |||
、入口和出口参数 | |||
、所用寄存器 | ;可选项,最好采用寄存器的保护和恢复方法,使之使用透明化 | ||
、所用额外存储单元 | ;可选项,可以减少为子程序定义自己的局部变量 | ||
、子程序的所采用的算法 | ;可选项,如果算法简单,可以不写 | ||
、调用时的注意事项 | ;可选项,尽量避免除入口参数外还有其它的要求 | ||
、子程序的编写者 | ;可选项,为将来的维护提供信息 | ||
、子程序的编写日期 | ;可选项,用于确定程序是否是最新版本 | ||
这些说明性信息虽然不是子程序功能的一部分,但其他程序员可通过它们对该子程序的整体信息有一个较清晰认识,为准确地调用它们提供直接的帮助,与此同时,也为实现子程序的共享提供了必要的资料。
7.2 子程序的调用和返回指令
子程序的调用和返回是一对互逆操作,也是一种特殊的转移操作。
一方面,之所以说是转移,是因为当调用一个子程序时,程序的执行顺序被改变,CPU将转而执行子程序中的指令序列,在这方面,调用子程序的操作含有转移指令的功能,子程序的返回指令的转移特性与此类似;
另一方面,转移指令是一种“一去不复返”的操作,而当子程序完后,还要求CPU能转而执行调用指令之下的指令,它是一种“有去有回”的操作。
为了满足子程序调用和返回操作的特殊性,在指令系统中设置了相应的特定指令。
7.2.1 调用指令(CALL)
调用子程序指令的格式如下:
CALL 子程序名/Reg/Mem
子程序的调用指令分为近(near)调用和远(far)调用。如果被调用子程序的属性是近的,那么,CALL指令将产生一个近调用,它把该指令之后地址的偏移量(用一个字来表示的)压栈,把被调用子程序入口地址的偏移量送给指令指针寄存器IP即可实现执行程序的转移。近调用指令的堆栈操作如图7.1所示。
图7.1 近调用指令进栈操作示意图
如果被调用子程序的属性是远的,那么,CALL指令将产生一个远调用。这时,调用指令不仅要把该指令之后地址的偏移量压进栈,而且也要把段寄存器CS的值压进栈。在此之后,再把被调用子程序入口地址的偏移量和段值分别送给IP和CS,这样完成了子程序的远调用操作。远调用指令的堆栈操作如图7.2所示。
图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所示)。 |
图7.3 近返回指令的出栈操作示意图
图7.4 远返回指令的出栈操作示意图
如果返回指令后面带有立即数(其值通常为偶数),则表示在得到返回地址之后,SP还要增加的偏移量,它不是类似于高级语言中子程序的返回值(如图7.5所示)。
图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中返回。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论