第十二章 子程序设计第 1节 子程序调用与返回指令
1,子程序调用指令
(1) 段内直接调用
格式,CALL过程名;过程名是个标号
CALL NEAR PTR 过程名
操作,SP← SP-2; [SP,SP+1] ← IP;保护断点 IP← 子程序入口偏移地址;转子程序
(2) 段内间接调用格式,CALLOPRD;OPRD≡ {R,M(字) }
操作,SP← SP-2;[SP,SP+1] ← IP;保护断点
IP← [EA];EA为依寻址方式所得有效地址例如:
CALLBX;
CALLWORD PTR [BX];
CALLDAT1; DAT1为字变量
CALLWORDPTR[BP][SI]
3) 段间直接调用格式,CALL FAR PTR过程名操作,SP← SP-2; [SP,SP+1] ← CS;保护断点
SP← SP-2; [SP,SP+1] ← IP;
CS ← 子程序入口段地址;转子程序入口
IP← 子程序入口偏移地址;
(4) 段间间接调用格式,CALLOPRD; OPRD≡ {M(双字) }
操作,SP← SP-2; [SP,SP+1] ← CS;保护断点
SP← SP-2; [SP,SP+1] ← IP
CS← OPRD的段地址;转子程序入口
IP← OPRD的偏移地址;
例如:
CALLDWORD PTR [BX];
CALLDAT2; DAT2为双字变量
CALLDWORDPTR[BP][SI]
2,子程序返回指令格式 1,RET;
功能:放在子程序末尾,依 CALL指令的属性(段内或段间)
返回调用程序的断点处继续执行。
操作:段内调用的 RET指令
IP← [SP,SP+1]; SP← SP+2;恢复断点段间调用的 RET指令
IP← [SP,SP+1]; SP← SP+2;恢复断点
CS← [SP,SP+1]; SP← SP+2;
格式 2,RET n;
操作:与 RET指令相同,但执行恢复断点操作后,
SP ← SP+n;
第 2节 子程序的结构形式
1,子程序的说明文件
建立子程序的文档,说明该子程序的功能和调用方法
2,子程序的现场保护和现场恢复
利用堆栈进行现场保护和现场恢复
3,子程序的调用和返回子程序设计中注意事项
1,要正确使用堆栈( PUSH和 POP指令要配对使用),否则将导致不能返回的错误;
2,要注意保护和恢复数据(保护需要保护的寄存器或 FR的内容,用于传递参数的寄存器可以不保护)
例如,
SUB1 PROC FAR
PUSHAX;
PUSHBX;
PUSHCX;
PUSHDX;保护数据
:(子程序实际内容)
POPDX;
POPCX;
POPBX;恢复数据
POPAX;(后进先出)
RET;
SUB1 ENDP;
第 3节 参数传递方法
主程序在调用子程序前,常需要向子程序传送一些数据,
这些数据称为入口参数。当子程序执行完后,要将执行的结果传回给主程序,这些数据称为出口参数。
参数传递的方法主要有约定寄存器法、约定存储器法与堆栈法三种。
1.约定寄存器法约定寄存器法直接利用 CPU内部寄存器传送参数。在转向子程序之前,主程序将入口参数送入指定寄存器中;调用子程序后,子程序从指定寄存器引用这些参数。经加工处理的结果也放入指定的寄存器中,这样从子程序返回主程序后可以从指定的寄存器中获得处理结果。这种方法最简单,但由于寄存器资源有限,故仅适用传递参数较少的情况。
【 例 7— 4】 编写程序,将字符串 STRl中的大写字母字符取出送字符串
STR2,并将字符串 STR2在显示器上显示。
将判断字符是否为大写字母字符编一子程序。为此,应将字符串 STRl、
STR2的首地址和字符串 STRl的长度送寄存器作为入口参数传递给子程序。
程序流程图如下图所示
2.约定存储器法约定存储器法是在存储器中专门指定一些单元存放入口参数和出口参数。在转子程序之前,主程序用数据传送指令将入口参数存入入口参数的存储单元中。调用子程序后,子程序按照约定从入口参数存储单元中取出这些参数进行处理。子程序完成处理,
将处理结果送入指定的出口参数存储单元中,返回后主程序可从这些特定存储单元中获取需要的处理结果。
约定存储器法传送速度比约定寄存器法要慢些,适合于传递参数较多的情况。
【 例 7-5】 将例 7— 4改用约定存储器法处理。
与例 7— 4比较只需将欲传送的 STRl和 STIl2的首地址及 STRl的长度分别送入存储单元 PARA1,PARA2和 PARA3程序如下:
中。
DATA SEGMENT
STRl DB 'AB1239CDEF
COUNT EQU $-STR1
STR2 DB COUNT+1 DUP(?)
PARA1 DW?;定义约定的存储器单元
PARA2 DW?
PARA3 DW?
DATA ENDS
STACK SEGMENT PARA STACK 'STACK'
DB 100DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,ES:DATA
MAIN PROC FAR;主程序
BEGIN PUSH DS
MOV AX,0
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV ES,AX
LEA SI,STR1
MOV RARA1,SI;PARA1中为字符串 S1的首地址
LEA DI,STR2
MOV PARA2,DI; PARA2保存 STR2的首地址
MVO PARA3,COUNT; PARA3保存 STR1的字符个数
CALL FIND;调用子程序
MOV DX,OFFSET STR2;显示字符串 S2
MOV AH,09H
INT 21H
RET
MAIN ENDP;子程序名; FIND;功能:将字符串中的字母字符取出,送 STR2;输入参数,PARA1---字符串 STR1的首地址; PARA2---字符串 STR2的首地址; PARA3---STR1R的长度;输出参数:字符串 STR2
FIND PROC NEAR;定义子程序
PUSH AX;保护现场,其他寄存器也可考虑加以保护
MOV SI,PARA1;从约定存储单元中获取加工数据
MOV DI,PARA2
MVO CX,PARA3
LOOP1,MOV AL,[SI]
CMP AL,‘ A’,判断字符是否 >‘A’
JB LOOP2;小于则是数字,转取一个
MOV [DI],AL;将字母字符保存至 STR2中
INC DI;指向 STR2的下一字置
LOOP2,INC SI;指向 STR1的一下字符
LOOP LOOP1
MOV BYTE PTR[DI],L‘$’
POP AX;恢复现场
RET ;返回
FIND ENDP
CODE ENDS
END BEGN
【 例 7— 6】 编写程序,计算两个 64位数的和,不考虑和的进位。
由于 64位数占用的寄存器比较多,故适合于用约定存储单元法来进行处理。
程序中将 64位加数 mMl和 M,M2存放在两个双字单元内。由于不考虑和的进位,所以和也可用两个双字单元来存放。
程序如下:
DATA SEGMENT
NUMl DD 1234H,12345678H,M7Ml为 123456781234H
MM2 DD 432lH,8765432lH,NIM2为 87654321432lH
SUM DD?,?
DATA ENDS
STACK SEGMENT PARA STACK’STACK’
DB 100 DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME CS,CODE,SS,STACK,DS,DATA,ES,DArA
MAIN PAR:主程序
BEGIN,PUSH DS
MOV AX,0
PUSH AX
MOV AX,D』 uA
MOV DS,AX
MOV ES,AX
CALL ADD64;调用子程序 ADD64
RET
MAIN ENDP
:子程序名称,ADD64;子程序功能:两个 64位数的相加;输入参数,MUMl一被加数的地址; NUM2一加数地址
:输出参数,SUM一和数地址
ADD64 PROC
PUSHF
PUSH AX:保护现场
PUSH BX
PUSH CX
LEA BX,Mmll,Bx指向被加数单元
MOV CX,4
CLC
AGAIN,MOV Ax,[Bx】 ;取被加数的相应字一 Ax
ADc Ax,[Bx+8】 ;与加数相应字相加
MOV 【 Bx+lOH】,Ax;保存和
INC BX
INC BX
LOOP AGAIN
POP Cx:恢复现场
POP BX
POP AX
POPF
RET
ADD64 ENDP
CODE ENDS
END BEGIN
3.堆栈法堆栈法使用堆栈来完成数据传递。在调用子程序之前将参数压入堆栈,
转入子程序后通过
sP指针获得压入堆栈的参数地址,从而将其取出。子程序的处理结果亦送入堆栈保存。返回主程序后再通过出栈指令取出结果。使用该方法时应明了堆栈的变化情况,
特别注意参数入栈与出栈顺序。
堆栈法适合于参数较多且子程序有嵌套、递归调用的场合。
【 例 7— 7】 编写程序,调用子程序求一数组元素的和。
程序如下:
DAllA SEGMENT
NuM DB 08H,01H,34H,59H,76H,98H:定义数组
COUNT EQU $-NIH讧
SUM0 Dw?;存放和
DATA ENDS
S11ACK SEGMENTⅣ LRA STACK’S1ACl(’
DB 100H DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME CS,CODE,SS,S1ACK,DS,DATA
MAIN PROC FAR:主程序
BEGIN,PUSH DS
MOV AX,O
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV AX,COUNT
PUSH AX;数组的个数入栈
LEA AX,NUM
PUSH AX;数组的首地址入栈
CALL SUM;调用子程序求和
RET
MAIN ENDP
SUM
AGAIN:
SUM
CODE
PROc;求和子程序
PUSH AX;保护现场
MOV CX,[BP+12。 ]; CX一数组的个数
MOV BX,[BP+10】 ; BX一数组首址的偏移量
XOR AX,AX; AX— O
ADD AL,[Bx】 ;累加求和
ADC AH,0;累加进位
INC BX;指向下一数据
LOOP AGAIN
MOV[Bx】,AX;结果保存至 SUM0单元
POPF
POP BP;恢复现场
POP CX
POP BX
POP AX
RET 4;修改 sP,保持堆栈平衡
ENDP
ENDS
END BEGIN
主程序将数组的个数以及数组的首地址压入堆栈,再调用子程序;在子程序中,根据 sP指针获得压入堆栈的参数地址。应注意调用子程序时,
会将断点的 IP(段内调用 )或 IP和 CS(段问调用 )压入堆栈。当子程序结束返回时,需用带偏移量的返回指令 RET 4,这是因为参数是通过压入堆栈的方式传入子程序,而子程序并未将其弹出,故为保持堆栈平衡 (该数据已无用 )而将 SP指针修改。
图 7— 3给出了执行过程中堆栈的变化情况。图 7— 3(1)是调用子程序之前将相关参数压入堆栈的情况;图 7— 3(2)则是执行调用指令转入子程序时,将断点 IP压入堆栈后的情况。由于子程序与主程序同在一代码段中,故只将断点 IP入栈。图 7— 3(3)是转入子程序后,利用 堆栈保护现场保护后的堆栈状况。
在程序中,将 BP入栈后即将 SP值赋给 BP,所以 BP+]元内容应为主程序中压入的数组首址,如此即取出传入参数。图 7— 3(4)是现场恢复后堆况,
此时 sP指针指向断点 IP,正确返回。图 7— 3(5)是正确返回后,对 SP指针修改,占用堆栈空间。
第 4节 子程序的嵌套与递归
(1) 子程序的嵌套
子程序嵌套是指子程序本身再次调用别的子程序堆栈示意图
(2) 子程序的递归
子程序递归指子程序调用子程序自己本身第 5节 子程序设计方法
1,过程( PROCEDURE)定义伪指令
格式:过程名 PROC[类型 ]
:过程(子程序)内容过程名 ENDP
过程属性
①段属性 —— 过程所在的段地址
②偏移属性 —— 过程第一条指令的偏移地址
③类型属性 —— 段内近程 NEAR和段间远程 FAR属性
例如,SUB1PROCFAR
,
SUB1ENDP
第 6节 子程序设计举例