在实际程序中,经常要进行一些重复运算,许多不同的程序也经常用到相同的程序,因此,有必要把一些程序编制成相对独立的程序段,放在内存中公用,这些可以反复使用的公用程序,称为子程序。
设计子程序时,要考虑如下主要问题:
①主程序怎样调用子程序?
②子程序执行完成后,如何正确返回调用语句?
③如何保护和恢复子程序中可能破坏的寄存器、存储单元的内容?
④主程序与子程序如何传送参数?
⑤如何编制子程序?
1.子程序的调用和返回
CALL和RET指令的功能是完成子程序的调用和返回。子程序的正确调用和正确返回才能保证子程序的正确执行。为保证其正确性,除PROC的属性要正确选择外,还应注意子程序运行期间的堆栈状态,由于执行CALL指令时CPU已使返回地址入栈,所以执行RET指令时应该使返回地址出栈,如果子程序中不能正确使用堆栈而造成执行RET前SP并未指向返回地址,则必然会导致程序出错,因此在子程序中,对堆栈的应用应特别小心,以免发生错误。
图4-7给出了调用FAR类型的子程序时CALL和RET指令的堆栈操作。
图4-7 调用FAR类型的子程序时CALL和RET指令的堆栈操作
a)CALL指令执行后 b)RET指令执行后
调用NEAR类型的子程序时,只保存和恢复IP的内容,CS的内容不变。
2.保存和恢复寄存器
由于主程序和子程序经常是分别编制的,所以它们所使用的寄存器往往会发生冲突。如果主程序在调用子程序之前的某个寄存器内容在子程序返回后还有用,而子程序又恰好使用了同一个寄存器,则破坏了该寄存器的原有内容,从而造成程序运行的错误。为了保证程序不会出错,在进入子程序之后首先应该把主程序使用的寄存器保存在堆栈中,而在退出子程序之前,再将寄存器内容恢复原状。典型的保护和恢复寄存器编程如下:
需要说明的是,堆栈的工作方式是先进后出,因此进行保护和恢复时要特别注意进出的顺序。另外,设计子程序时,应仔细考虑哪些寄存器是必须保护的,哪些寄存器是不必要或不应保护的。一般来说,子程序中用到的寄存器是应该保护的。但是如果使用寄存器在主程序和子程序之间传递参数,那么这种寄存器就不一定要保护,特别是用来向主程序回送结果的寄存器,就更不应该因保护和恢复寄存器而破坏了应该向主程序传送的信息。
存储单元的保护和恢复可参照上述保护和恢复寄存器的方法。
3.主程序和子程序之间的参数传送方法
主程序在调用子程序时,经常需要传送一些参数给子程序;子程序运行完后也经常要回送一些信息给主程序。这种主程序和子程序之间的信息传送称为参数传送。参数传送的方法主要有以下几种:
①通用寄存器传送参数。这是一种最常用、最方便的方法。该方法是主程序调用子程序时,将要传送的参数装入某些指定寄存器;子程序返回时也将结果装入指定的寄存器。
②存储单元传送参数。指定某些存储单元存放主程序与子程序之间要传递的参数。
③堆栈传送参数。该方法与存储单元传送参数方法类似,应用时应注意对堆栈的操作,避免数据混乱。(www.xing528.com)
4.子程序说明信息
为了能正确地使用子程序,编写子程序时应对其基本信息进行说明。子程序的说明信息至少应该包含下面几个部分:
有了上述说明信息,即使用户不了解子程序的源代码,也能方便地调用它。
下面我们通过一个实例来说明子程序的设计方法。
例4-21 编写一个对字型数组各元素求和的子程序(不考虑进位),并在主程序中调用该子程序,分别求字型数组A1和A2的各元素之和。
为方便读者理解和比较,下面分别用三种参数传递方法编写该程序。
(1)使用通用寄存器传递参数
说明:在主程序中两次调用子程序分别对数组A1和数组A2求和。注意两次调用之前,程序中对要传递的参数(子程序的入口参数)必须事先安排,二者的入口参数数值是不一样的。
由于通用寄存器的个数有限,所以当参数个数较多时,不适合使用此方法,而应当借助于存储单元来传递参数。
(2)使用存储单元传递参数
说明:使用存储单元传递参数,需要在数据段定义相应的存储空间。如本例中,先在数据段定义了一个3个字的参数表,每次调用子程序前,一定要向参数表中填入正确的参数。
(3)使用堆栈传递参数
说明:子程序借助指针寄存器BP,采用寄存器相对寻址方式取得入口参数,位移量与入口参数的入栈顺序、子程序类型以及寄存器的保护等因素均有关系。本例中的子程序取得入口参数时,堆栈的情形如图4-8所示。另外,子程序返回时,使用了带立即数返回指令RET 6,其功能是返回之前自动执行ADD SP,6语句,其目的是废除入口参数,恢复堆栈平衡。
图4-8 取得入口参数时堆栈的情形
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。