6.1子程序调用与返回6.2寄存器保护与恢复6.3过程定义6.4子程序参数传递:寄存器、内存、堆栈6.5子程序举例第六章子程序结构在编写汇编语言源程序时,把实现独立功能的程序段编写为子程序。在主程序中调用子程序,可以改善程序的可读性和可维护性。子程序中至少要有一条RET指令,主程序一般应通过CALL指令调用子程序,。如图:主程序子程序MOVAX,10H...CALLXXXMOVCX,BX..CALLXXXMOVCX,BX..XXX:SHLAX,1....RET6.1子程序调用与返回在编程时容易犯的错误:正确的方法:MOVAX,10H..CALLXXXMOVCX,BX..CALLXXXXXX:SHLAX,1..RETMOVAX,10H..CALLXXXMOVCX,BX..CALLXXXHLTXXX:SHLAX,1..RET在编程时容易犯的错误:正确的方法:MOVCX,10H..SNT:CALLYYYADDAX,BX..YYY:SHLAX,1..RETLOOPSNTHLTMOVCX,10H..SNT:CALLYYYADDAX,BX..LOOPSNTHLTYYY:SHLAX,1..RET通过CALL指令执行子程序,为什么在执行RET指令时能够返回到CALL指令的下一条指令?是因为在执行CALL指令时,把返回地址保存在了堆栈中,在子程序的末尾,RET指令从堆栈中取出返回地址,从而返回到原来CALL指令的下一条指令执行。以段内调用子程序为例,CALL指令执行时堆栈变化如下:SP--SP-2(SS:SP)--IP在子程序末尾执行段内返回RET操作如下:IP--(SS:SP)SP--SP+2下面看演示:CALL指令有段内调用和段间调用两种,段间调用CALL指令执行时堆栈变化如下:SP--SP-2(SS:SP)--CSSP--SP-2(SS:SP)--IP在子程序末尾执行段间返回RET操作如下:IP--(SS:SP)SP--SP+2CS--(SS:SP)SP--SP+2下面看演示:在子程序中有时候需要使用寄存器,有时候会与主程序使用寄存器发生冲突,这就需要在进入子程序后保护这些寄存器,而在子程序结束时恢复这些寄存器的内容。例如:要求用右移的方法把数组ARRY中的20个元素除以8,元素是16位有符号数。我们把16位有符号数除以8的部分编写成子程序DIV8:子程序名:DIV4入口参数:AX中为16位有符号数出口参数:AX中为入口参数的八分之一DIV:MOVCL,3SARAX,CLRET6.2寄存器保护与恢复主程序段:MOVBX,OFFSETARRYMOVCX,20D8Y:MOVAX,[BX]CALLDIV8;AX内容除以8MOV[BX],AXINCBXINCBXLOOPD8Y;后续程序可以看到,上面主程序使用了CX,而子程序也使用了CL,发生了冲突,因此在调用子程序时,应该保护CX。可以在主程序中保护,也可以在子程序中保护。在主程序中保护:MOVBX,OFFSETARRYMOVCX,20D4Y:MOVAX,[BX]PUSHCXCALLDIV4POPCXMOV[BX],AXINCBXINCBXLOOPD4Y;后续程序也可以在子程序内部保护寄存器,即在入口处保护寄存器内容,在返回前恢复寄存器内容。例如:子程序名:DIV8入口参数:AX中为16位有符号数出口参数:AX中为入口参数的八分之一注:CX不改变DIV8:PUSHCXMOVCL,3SARAX,CLPOPCXRET如果需要保护的寄存器数量多,应该遵循”后进先出“的原则:如:PUSHAXPUSHBXPUSHSI...POPSIPOPBXPOPAXRET子程序CALL指令有段间调用和段内调用之分,RET指令也有段间返回和段内返回之分,但是返回指令形式都是RET。那么,一个子程序的RET指令到底是段间返回还是段内返回呢?8086汇编语言采用过程定义来说明这个问题。8086汇编语言中,可以把子程序定义为过程,并使用伪指令NEAR指明是近过程或用FAR指明是远过程。一个过程可以被其他程序所调用。对近过程的调用和返回,汇编程序按近调用和近返回处理;对远过程的调用和返回,汇编程序按远调用和远返回处理。定义过程的伪指令有PROC、ENDP、NEAR和FAR定义过程的伪指令PROC和ENOP总是成对出现的,这两条伪指令中间的内容作为一个过程,即一个子程序。6.3过程定义6.3.1、近过程定义一般形式如下:代码段名SEGMENTASSUME语句START:..CALL过程名.HLT过程名PROCNEAR..RET过程名ENDP代码段名ENDSENDSTART在同一个代码段中,如果不定义过程,直接使用子程序,则汇编程序按照近调用和近返回处理。主程序过程定义代码段定义6.3.2、远过程定义一般形式如下:代码段名1SEGMENTASSUME语句START:..CALL过程名.HLT代码段名1ENDS代码段名2SEGMENTASSUME语句过程名PROCFAR..RET过程名ENDP代码段名2ENDSENDSTART主程序过程定义代码段定义另一个代码段定义子程序的参数传递包括入口参数传递和出口参数传递。参数传递的方法有下面几种:◆寄存器传递参数◆内存单元传递参数◆堆栈传递参数6.4.1、寄存器传递参数寄存器传递参数,例如前面讲过的DIV4,用AX传递入口参数和出口参数。。因为寄存器的数量有限,这种方法只适合参数很少的子程序。6.4.2、内存单元传递参数主程序在内存的数据段建立一个参数表,在调用子程序时,把参数表的首地址通过指针传递给子程序。如:6.4子程序的参数传递例:在数据串STR1中有20个字数据,数据串STR2中有5个字数据,编程。在STR1中查找子串STR2,找到则把BL置为1,否则把BL置为0。先编一个比较字数据串的子程序CMPSTR。子程序名:CMPSTR功能:把STR2与SI指到的字数据串比较(只比较前5个元素),相同则返回ZF=1,否则返回ZF=0。入口参数:SI指到一个数据串。出口参数:两个串相同则返回ZF=1,否则返回ZF=0程序:CMPSTR:LEADI,STR2MOVCX,5CLDREPZCMPSWRET主程序段:MOVAX,DATAMOVDS,AXMOVES,AXLEADX,STR1MOVCX,16;最多比较20-5+1=16次MOVBL,1SLP1:MOVSI,DXPUSHCXCALLCMPSTRPOPCXJZSLE1;如果STR2是STR1的子串,则ZF=1。ADDDX,2LOOPSLP1DECBLSLE1:HLT6.4.3、堆栈传递参数子程序可以利用堆栈传递参数。例如C语言中,函数的参数传递就是利用堆栈。首先要了解堆栈的构造和工作原理:堆栈段使用段寄存器SS。在CALL指令、RET指令、PUSH指令、POP指令中,按照“后进先出”的原则工作,并使用SP寄存器内容为堆栈顶偏移量指令。使用BP为指针存取数据,默认的段寄存器也是SS,因此,BP也常常用来作存取堆栈中数据的偏移量指针。堆栈顶是变化的,随着压栈操作,堆栈顶向低地址方向生长。对堆栈的压栈操作和弹出堆栈操作必须平衡。利用堆栈传递子程序参数的方法是:主程序把参数压入堆栈,子程序从堆栈中取得参数,子程序返回时要使堆栈达到平衡。教材P206例6.4:在数据段ARY数组中有100个16位数据,数组元素的个数放在COUNT单元中。对数组元素求和,和数为16位,和数存放到SUM单元。要求用堆栈传递ARY首地址及COUNT单元地址及SUM单元地址。主程序段:MOVAX,DATAMOVDS,AXLEAAX,ARYPUSHAX;ARY首地址压入堆栈LEAAX,COUNTPUSHAX;COUNT地址压入堆栈LEAAX,SUMPUSHAX;SUM地址压入堆栈CALLPROADDHLT;后续程序下面分析一下程序流程进入PROADD子程序前后堆栈的情况:最初状态:三次AX压栈后:CALL指令执行后:SP低地址高地址××××####%%%%SPARY地址SUM地址COUNT地址××××####%%%%◎◎◎◎SPARY地址SUM地址COUNT地址调用指令下面一条指令的地址即子程序返回的地址。子程序:通常使用BP指针存取堆栈中的数据PROADDPROCNEARMOVBP,SPMOVBX,[BP+6];取ARY首地址MOVCX,[BP+4];取数据个数SUBAX,AXLP:ADDAX,[BX]INCBXINCBXLOOPLPMOVSI,[BP+2];取SUM地址MOV[SI],AX;存和数RET6;返回,并且使SP=SP+6PROADDENDP子程序中BP为指针存取数据的情况:××××####%%%%◎◎◎◎BP+6BP+4BP+2BPARY地址SUM地址COUNT地址调用指令下面一条指令的地址,即子程序返回的地址。高地址低地址RET指令取返回地址,SP指针再加6,返回主程SP指针加2后的情况:序后的情况:××××####%%%%◎◎◎◎SPARY地址SUM地址COUNT地址××××####%%%%◎◎◎◎SP高地址低地址问题:取堆栈中的数据能否使用BX、SI、DI作指针?答:可以,但是必须用段超越。如:PROADDPROCNEARMOVSI,SPMOVBX,SS:[SI+6];取ARY首地址MOVCX,SS:[SI+4];取数据个数SUBAX,AXLP:ADDAX,[BX]INCBXINCBXLOOPLPMOVBX,SS:[SI+2];取SUM地址MOV[BX],AX;存和数RET6;返回,并且使SP=SP+6PROADDENDP子程序的6.5子程序举例1、多位BCD码转换为无符号数4字节压缩的BCD码转换为4字节无符号数。如:83174009H转换为04F52279H例:数据区D_BUF存有4字节压缩的BCD码,把它转换为4字节无符号数存放到B_BUF区。先编写一个把BCD码最高位移到AL的低位的子程序D_LEFT。例如:D_BUF区内容为83174009H,则D_LEFT执行1次后,AL=08H,D_BUF区内容为31740090H。子程序名:D_LEFT入口参数:SI指到D_BUF区首地址。出口参数:AL中为D_BUF区最高字节BCD码的高位,D_BUF区内容左移4位子程序名:D_LEFT入口参数:SI指到D_BUF区首地址。出口参数:AL中为D_BUF区最高字节BCD码的高位,D_BUF区内容左移4位,CX不变。D_LEFT:PUSHCXXORAL,ALMOVCX,4D_L1:SHLWORDPTR[SI],1RCLWORDPTR[SI+2],1RCLAL,1LOOPD_L1POPCXRET第二个子程序实现把B_BUF区内容乘10,乘积仍然放B_BUF区。子程序名:B_MUL入口参数:SI指到B_BUF区首地址,AL中为其中一个因子。另一个在B_BUF区。出口参数:乘积在B_BUF区,CX不变。B_MUL:PUSHCXMOVCX,4MOVBL,0CLCPUSHFB_M1:moval,10MULBYTEPTR[SI];[SI]*10----AXPOPFADCal,bl;部分积的低位带进位加上次的部分积高位ADCah,0;低位相加的进位加到高位上mov[SI],almovbl,ahPUSHFincSILOOPB_M1POPFPOPCXRET第三个子程序实现把B_BUF区内容加AL内容,和数仍然放B_BUF区。子程序名:B_ADD入口参数:SI指到B_BUF区首地址,AL中为其中一个加数。另一个在B_BUF区。出口参数:B_BUF区中为得到的和,CX不变。B_ADD:MOVAH,0ADD[SI],AXADCWORDPTR[SI+2],0RET完整的程序:DATASEGMENTD_BUFDB09H,40H,17H,83HB_BUFDB4DUP(0)DATAENDSCODESEGMENTASSUMECS:CODE,DS:DATASTART:MOVAX,DATAMOVDS,AXMOVC