版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第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远调用指令进栈操作示意图子程序调用指令本身的执行不影响任何标志位,但子程序体中指令的执行会改变标志位,所以,如果希望子程序的执行不能改变调用指令前后的标志位,那么,就要在子程序的开始处保护标志位,在子程序的返回前恢复标志位。例如:[BX][BX]CALLDISPLAYCALLBXCALLWORD1[BX][BX]CALLDISPLAYCALLBXCALLWORD1CALLDWORD1CALLwordptr;DISPLAY是子程序名;BX的内容是子程序的偏移量;WORD1是内存字变量,其值是子程序的偏移量;DWORD1是双字变量,其值是子程序的偏移量和段值;BX所指内存字单元的值是子程序的偏移量CALLdwordptr;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带立即数的返回指令的出栈操作示意图在MASM5.0及其以后版本中,可用指令RETN或RETF来显式地告诉汇编程序是本子程序的返回是近返回,还是远返回。例如:RET;可能是近返回,也可能是远返回RETN;近返回指令RETF;远返回指令RET6;子程序返回后,(SP)^(SP)+6例7.1编写一个子程序UPPER,实现把寄存器AL中存放的字符变大写。解:;子程序功能:把AL中存放的字符变大写;入口参数:AL;出口参数:AL;算法描述:判断AL中字符必须在'a'~'z'之间才能把该字符变为大写UPPERPROCCMPAL,'a' ;书写'a'的ASCII码61H也可以JBoverCMPAL,'z'JAoverSUBAL,20H ;书写指令ANDAL,0DFH也可以over:RETUPPERENDP例7.2编写一个求字符串长度的子程序StrLen,该字符串以0为结束标志,其首地址存放在DS:DX,其长度保存在CX中返回。解:;子程序功能:求字符串的长度;入口参数:DS:DX存放字符串的首地址,该字符串以0为结束标志;出口参数:CX存放该字符串的长度;算法描述:用BX来指针来扫描字符串中的字符,如果遇到其结束标志,则停止扫描字符串操作StrLenPROCPUSHAXPUSHBX;用堆栈来保存子程序所用到的寄存器内容XORCX,CXXORAL,ALMOVBX,DXagain:CMP[BX],ALJZoverINCCX;增加字符串的长度INCBX;访问字符串的指针向后移JMPagainover:POPBX;恢复在子程序开始时所保存的寄存器内容POPAXRETStrLenENDP7.3子程序的参数传递子程序一般都是完成某种特定功能的程序段。当一个程序调用一个子程序时,通常都向子程序传递若干个数据让它来处理;当子程序处理完后,一般也向调用它的程序传递处理结果,我们称这种在调用程序和子程序之间的信息传递为参数传递。用程序向子程序传递的参数称为子程序的入口参数,子程序向调用它的程序传递的参数称为子程序的出口参数。子程序的入口参数和出口参数都是任意项,对某个具体的子程序来说,要根据具体情况来确定其入口和出口参数,也可以二者都没有。程序和被调用子程序之间的参数传递方法是程序员自己或和别人事先约定好的信息传递方法。这种信息传递方法可以是多种多样的,在本节,我们只介绍
常用的、行之有效的参数传递方法有:寄存器传递参数、约定存储单元传递参数和堆栈传递参数等。如果对其它的参数传递方法感兴趣的话,可参考其它《汇编语言程序设计》书籍。7.3.1寄存器传递参数一方面,由于CPU中的寄存器在任何程序中都是“可见”的,一个程序对某寄存器赋值后,在另一个程序中就能直接使用,所以,用寄存器来传递参数最直接、简便,也是最常用的参数传递方式。但另一方面,CPU中寄存器的个数和容量都是非常有限,所以,该方法适用于传递较少的参数信息。例7.1是用寄存器传递参数的例子,子程序处理的数据被保存在寄存器AL中。假设有下列的程序段:MOVCALLAL,'b'UPPER;子返回时,MOVCALLAL,'b'UPPER;子返回时,(AL)='B'MOVCALLAL,'2'UPPER;子返回时,AL的值不变,因为'2'不是字母例7.3按五位十进制的形式显示寄存器BX中的内容,如果BX的值小于0,则应在显示数值之前显示负号'-'。例如:(BX)=123,显示:00123;(BX)=-234,显示:-00234;解:;子程序功能:把寄存器BX的内容按十进制有符号数显示出来;入口参数:BX;出口参数:无,只有显示信息;算法描述:1、定义6个字节的存储单元2、 先判断BX是否小于零,如果是,则先显示负号'-',再取BX的绝对值;3、 采用除10,得余数的方法,从低位向高位求出每位十进制位;4、 输出数据的字符串。SubDataSEGMENTDB5DB5DUP('0'),0ah,0dh,'$';0ah、0dh:换行、回车SubDataDISPBXSubDataDISPBXENDSPROCDSDSDXCXAXASSUMEDS:SubDataPUSHPUSHPUSHPUSH
MOVAX,SubData;取子程序所用的数据区段地址MOVDS,AXCMPBX,0JGEnextMOVDL,'-'MOVAH,2INT21H;显示负号'-'NEGBX;求-BX,使其值为正数next:MOVSI,4MOVAX,BXMOVCX,10Dagain:XORDX,DXIDIVCX;DX存放余数,AX存放商ADDDL,'0'MOV[SI],DLDECSIJGEagainXORDX,DXMOVAH,9INT21H;调用中断21的功能9,显示DS:DX指向的字符串POPAXPOPCXPOPDXPOPDSRETDISPBXENDP7.3.2约定存储单元传递参数在调用子程序时,当需要向子程序传递大量数据时,因受到寄存器容量的限制,就不能采用寄存器传递参数的方式,而要改用约定存储单元的传送方式。这种参数传递方式有点象情报人员和联络人员之间的传递信息方式,一个向指定地点放情报,另一个从指定地点取情报。例7.2是采用约定存储单元传递参数的例子,所处理的数据不是直接传给子程序,而是把存储它们的地址告诉子程序。例7.4:编写一个子程序分类统计出一个字符串中数字字符、字母和其它字符的个数。该字符串的首地址用DS:DX来指定(以0为字符串结束),各类字符个数分别存放BX、CX和DI中。解:;子程序功能:分类统计出字符串中数字字符、字母和其它字符的个数;入口参数:DS:DX指向被统计的字符串
;出口参数:BX、CX和DI分别保存数字字符、字母和其它字符的个数;算法描述:1、当字符在'0'~'9'范围时,数字字符个数BX加1;2、 为了判断简单,先把字字母变成大写字母;3、 当字符在'A'~'Z'范围时,字母个数CX加1;4、 否则,其它字符个数DI加1。COUNTPROCPUSHAXPUSHSIXORBX,BXXORCX,CXXORDI,DI;上三条指令使各类字符计数清零MOVSI,DXagain:MOVAL,[SI]INCSICMPAL,0JEoverCMPAL,'0'JLotherCMPAL,'9'JGnextINCBX;数字字符个数加1JMPagainnext:CALLUPPER;调用子程序把AL中的字母变成大写字母CMPAL,'A'JLotherCMPAL,'Z'JGotherINCCX;字母个数加1JMPagainother:INCDI;其它字符个数加1JMPagainover:POPSIPOPAXRETCOUNTENDP例7.5显示出任意字符串中数字字符、字母和其它字符的个数。解:.MODELSMALL.DATAMSGDB'KSDJL0984/[]3oiuOIUOIU(*&(5341',0.CODE.STARTUPLEADX,MSG;DS:DX指向待统计的字符串CALLCOUNT;调用子程序统计出各类字符的个数CALLDISPBXMOVBX,CX;调用子程序显示数字字符的个数CALLDISPBXMOVBX,DI;调用子程序显示字母的个数CALLDISPBX.EXIT0END;调用子程序显示其它字符的个数7.3.3堆栈传递参数堆栈是一个特殊的数据结构,它通常是用来保存程序的返回地址。当用它来传递参数时,势必会造成数据和返回地址混合在一起的局面,用起来要特别仔细。具体做法如下:、当用堆栈传递入口参数时,要在调用子程序前把有关参数依次压栈,子程序从堆栈中取到入口参数;、当用堆栈传递出口参数时,要在子程序返回前,把有关参数依次压栈(这里还需要做点额外操作,要保证返回地址一定在栈顶),调用程序就可以从堆栈中取到出口参数。在通常情况下,我们用堆栈传入口参数,用寄存器传出口参数。1、用堆栈传递入口参数的调用方法:PUSHPara1・・・PUSHParan;把n个字的参数压栈CALLSUBPRO;调用子程序SUBPRO2、在子程序中取入口参数的方法:、段内调用子程序由于是段内调用,所以,CALL指令只把返回地址的偏移量(即IP的内容)压栈,如图7.6(a)所示。在进入子程序后,为了能读取传递过来的参数,需要用BP来访问堆栈,所以要先保护BP原来的值,再把当前SP的值传送给BP。于是,当前BP所指向的堆栈单元与最后一个参数Paran之间隔着BP的原值和返回地址的偏移量,也就是说:二者之间相差4个字节。具体情况如图7.6(b)所示。(a)、进入子程序时堆栈情况 (b)、子程序寄存器保护后的堆栈情况图7.6在段内调用情况下子程序所能访问的堆栈情况SUBPROPROCNEARPUSHBPMOVBP,SPSUBPROPROCNEARPUSHBPMOVBP,SPMOVParan,[BP+4];保护寄存器BP;用寄存器BP来访问堆栈,读取参数;保护其它寄存器的指令;保护其它寄存器的指令MOVPara1,[BP+4+2*(n-1)]SUBPROENDP、段间调用子程序在段间调用子程序时,CALL指令会把返回地址的偏移量和段寄存器CS的内容都压栈,如图7.7(a)所示。在进入子程序后,与前面“段内调用子程序”一样,也需要用BP来读取传递过来的参数,所以,也要先保护BP原来的值,再把当前SP的值传送给BP。这时,当前BP所指向的堆栈单元与最后一个参数Paran之间隔着BP的原值、返回地址的偏移量和段地址,所以,二者之间相差6个字节。具体情况如图7.7(b)所示。(a)、进入子程序时堆栈情况 (b)、子程序寄存器保护后的堆栈情况图7.7在段间调用情况下子程序所能访问的堆栈情况在段间调用时,除了多一个返回段地址外,其它的内容与“段内调用”的情况完全一致,所以,在读取第i个参数时,只要用[BP+6+4*(n-i)]代替[BP+4+2(n-i)]即可(假设每个参数都是字类型)。7.4寄存器的保护与恢复由于计算机的硬件资源只有一套,当子程序修改了寄存器的内容后,返回到调用它的程序时,这些寄存器的内容也就不会是调用子程序前的内容。这样,子程序修改寄存器内容就可能变成了调用它的副作用,这种副作用常常会导致调用程序的出错。为此,在编写子程序时,除了能对作为入口和出口参数的寄存器进行修改外,对其它寄存器的修改对调用程序来说都要是透明的,也就是说,在调用子程序指令的前后,除了作为入口和出口参数的寄存器内容可以不同外,其它寄存器的内容要保持不变。有时,也要求作为入口参数的寄存器内容保持不变。在子程序中,保存和恢复寄存器内容的主要方法是:在子程序的开始把它所用到的寄存器压进栈,在返回前,再把它们弹出栈。这样编写的好处是该子程序可以被任何其它程序来调用。在调用指令前,不需要保存寄存器,在调用指令后,也无需恢复寄存器。XXXXXPROCPUSHREG1XXXXXPROCPUSHREG1…PUSHREGn………POPREGn;把子程序要使用的寄存器压栈,REGi代表某个寄存器;子程序的处理功能语句;把前面压栈的寄存器弹出,注意它们的次序POPREG1RETXXXXXENDP例7.2就是一个在子程序中利用堆栈来保存和恢复寄存器内容的例子。利用堆栈来实现此项功能时,应注意以下几点:、用堆栈保存和恢复寄存器的内容,要注意堆栈“先进后出”的操作特点;、通常情况下不保护入口参数寄存器的内容,当然,也可以根据事先的约定而对它们加以保护;、如果用寄存器带回子程序的处理结果,那么,这些寄存器就一定不能加以保护;、整个子程序的执行几乎肯定要改变标志位,可用PUSHF和POPF来保护和恢复标志位,但一般在子程序中不保护标志位,除非有此特殊需要;7.5子程序的完全定义在7.1节所给出的子程序定义格式是一个最基本的、最简单的定义格式,它不能为子程序提供更简洁的调用方式。在宏汇编MASM6.11系统中,为微机汇编语言的子程序提供了更加丰富的定义方式。虽然子程序的这种定义方式显得稍微有点复杂,但它不仅为子程序的调用带来了极大的方便,而且其调用方式与高级语言中子程序的调用方式相一致,这就大大地降低了程序员熟练掌握它的难度。7.5.1子程序完全定义格式子程序PROC[distance][langtype][visibility][<prologuearg>]名 [USES寄存器列表〕[,参数[:数据类型]]...[LOCALvarlist]子程序的程序体子程序-ENDP名定义子程序时,可使用参数表来直接指明其所要的参数,但程序员必须先用也些也指令,或使用<langtype>参数来说明本子程序所使用的程序设计语言类型。程序员在定义子程序时,最好能象在高级语言(如:C/C++)定义过程那样,先说明该子程序的原型(用伪指令PROTO),这样,在调用时,系统可以自动进行类型检查,也可以使用更方便的调用伪指令INVOKE来调用该子程序。有关子程序的原型说明伪指令和调用伪指令在随后第7.5.8和7.5.9小节中加以介绍。子程序通常用RET指令来结束其执行,也可用指令“RETn”来指明在结束子程序执行后从堆栈弹出n个字节。有关返回指令请参阅7.2.2节中的叙述。汇编程序在处理子程序时能自动产生“起始”代码(PROLOGUECode)和“结束”代码(EPILOGUEcode)。这两段特殊的代码分别完成:在调用子程序时,能把传递给子程序的参数压栈,在子程序结束时能把先前压栈的参数弹出。有了这两段代码,程序员在调用子程序时就不用自行考虑子程序的参数传递问题。若子程序用指令RETN、RETF或IRETF作为子程序的结束指令,那么,汇编程序将不生成“结束”代码。程序员可以用自己定义宏来替代缺省的“起始”和“结束”的代码段。这种替代方法是使用伪指令:OPTIONPROLOGUE和OPTIONEPILOGUEo若子程序没有参数、局部变量,没使用USES子句,也不会产生新的段或段组,那么,子程序是可以嵌套定义的。程序员也可以使用返回指令RETN和RETF来避免子程序的嵌套。在子程序内部,可以在指令之前使用伪指令LOCAL来说明其局部变量,有关规定在随后的第7.5.10节中有详细的说明。下面就来介绍该定义格式中各个说明项的作用。7.5.2子程序的位距子程序的位距(Distance)有:Near、Far、Near16、Far16、Near32和Far32。子程序位距描述符告诉汇编程序该子程序是在本段之内(Near),还是在本段之外(Far)。Near和Far描述符表示使用当前的段规模(SegmentSize),Near16、Far16、Near32和Far32描述符是告诉汇编程序忽略当前的段规模,而使用指定16位或32位的段规模。若选用类型Near或Far,那么,汇编程序将根据当前段的规模来决定选用16位,还是32位的Near或Far。若程序员不指定该选项,那么汇编程序将根据当前的存储模式(由.MODEL来决定)和处理机类型来决定子程序类型。若不使用伪指令.MODEL,那么,Near是缺省的类型。7.5.3子程序的语言类型子程序语言类型(LanguageType)可以是任何一种有效的程序设计语句类型,由它来告诉汇编程序将使用什么样的标识符的命名风格、子程序的调用和返回约定。该语言类型说明可使汇编语言程序与其它语言程序达到共享的目的。所有有效的语言类型及其书写规定如表7.1所列。表7.1语言类型及其书写规定SYSCALLSTDCALLBasicFortranPascalXXXnXXXXXXXXXX字母大写化XXXXXXXXXX参数从左到右参数从右到左调用程序清空堆栈保存指针寄存器BP使用VARARG参数*若使用:VARARG参数,则调用程序清空堆栈,否则,被调用的子程序清空堆栈。程序员可用另外三种方法来设置程序的语言类型:.MODEL、OPTIONLANGTYPE:和命令行选项/Gx。若在程序和命令行中都说明了语言类型,那么,前者的说明优先。另外,程序员也可用命令行选项/H来限定标识符的最大长度。例如:Pascal语言风格:OPTIONLANGUAGE:PASCAL、/GcC语言风格:OPTIONLANGUAGE:C、/Gd7.5.4子程序的可见性子程序的可见性(Visibility)决定该子程序对其它模块是否可用。它共有三个属性值:PRIVATE、PUBLIC和EXPORToPUBLIC属性是子程序标准的缺省属性,但该缺省属性可以用伪指令OPTIONPROC来修改。EXPORT属性意味着该子程序是一个“远”的、具有PUBLIC属性的子程序,并要求连接程序在生成可执行文件时把其入口地址放入导出入口地址表中。例如:OPTIONPROC:PRIVATE ;说明子程序的可见性为:PRIVATEOPTIONPROC:EXPORT ;说明子程序的可见性为:EXPORT7.5.5子程序的起始和结束操作当程序员想用自己定义的宏来替代缺省的“起始”和“结束”的代码段时,可用下列说明语句来实现:OPTIONPROLOGUE:MacroNamelOPTIONEPILOGUE:MacroName2PROLOGUE和EPILOGUE分别指定MacroName1和MacroName2为“起始”和“结束”代码段的宏名。汇编程序对用户定义的宏MacroName1和MacroName2的形式有较严格的规定,要求宏的定义形式如下:MacroNameMACROProcName,flags,argbytes,localbytes,<reglist>,userparms:VARARG该宏定义的每个参数都有详细的说明,感兴趣的读者可看有关技术资料或MASM6.11中的帮助,详细的说明在此从略,但建议使用缺省的宏。如果想取消当前指定的宏名,而恢复使用缺省的“起始”和“结束”代码段的宏名,那么,可用下列说明语句,即指定二个缺省的宏名PrologueDef和EpilogueDef。OPTIONPROLOGUE:PrologueDefOPTIONEPILOGUE:EpilogueDef若程序员不要汇编程序自动产生“起始”和“结束”代码,则可用NONE来代替说明语句中的宏名,即:OPTIONPROLOGUE:NONEOPTIONEPILOGUE:NONE7.5.6寄存器的保护和恢复保护寄存器说明子句的说明格式:USES寄存器列表该说明子句要求汇编程序为其生成保护和恢复寄存器的指令序列,即:在进入子程序执行指令之前,把寄存器列表中的寄存器压进堆栈,在结束子程序执行时,把先前压进堆栈的寄存器弹出,以达到保护寄存器的目的。寄存器列表:列举出在子程序中需要保护的寄存器名,即:在子程序开始时需要把内容进栈的寄存器名。若有多个寄存器名,则在寄存器名之间要用“空格”来分开。例如:DsipPROCUSESAXDX,FUNC:WORD,MSG:PTRBYTEMOVDX,MSGMOVAX,FUNCINT21HRETDispENDP汇编程序在处理该子程序时,会根据子句USES的作用,在第一条指令“MOVDX,MSG”之前,插入把寄存器AX和DX进栈的指令序列,即:PUSHAXPUSHDX而在返回指令RET之前插入把寄存器DX和AX的值弹出的指令序列,即:POPDXPOPAX注意:若子程序含有多个RET或IRET指令,那么,汇编程序在每个RET或IRET指令前都将增加相应的弹出堆栈指令序列。从子句USES的功能来看,它与前面7.4节“寄存器的包含与恢复”中所用的方法完全一致,所不同的是:用USE子句进行寄存器保护和恢复的代码是由汇编程序自动产生的,程序员不用关心如何去做,有点象高级语言的编程风格,而7.4节中的代码则是由程序员自己来安排的。7.5.7子程序的参数传递子程序参数是用来向子程序传递信息的数据。若有多个参数,则参数之间要用逗号分割。为了能说明子程序的参数,程序员必须事先指定参数所遵循的语言类型或使用“语言类型”参数。参数的数据类型可以是任何一个有效的数据类型说明符或VARARG。VARARG数据类型允许向子程序传递“个数”不定的参数,其参数之间要用逗号“,”来分开。若参数表中含有VARARG说明的参数,那么,该参数一定是该子程序的最后一个参数。其规定隐含地说明了在参数表中只能有一个用VARARG说明的参数。当子程序的语言类型是C、SYSCALL和STDCALL时,在其参数表中才能使用VARARG数据类型的参数。见前面的表7.1中所列。如果没有显式地指定某个参数的数据类型,那么,在16位段规模的情况下,其缺省的数据类型是WORD;在32位段规模的情况下,其缺省的数据类型是DWORDo7.5.8子程序的原型说明子程序原型的说明格式如下:子程序名PROTO[distance][langtype][,[parameter]:tag]...该说明语句告诉汇编程序该子程序的若干属性,如:位距、语语言类型、参数个数及其类型等。这样,汇编程序就可以对其定义进行适当的检查。如果对所有基于堆栈的过程都定义一个原型,那么,就可把这些原型存放在一个独立的包含文件(用伪指令INCLDUE来装入)中。使用这种方法对将来把所有子程序放入自定义的库文件中是非常方便的。该原型说明语句中参数distance、langtype、parameter和tag等的含义与前面的叙述相一致,在此不再重复。7.5.9子程序的调用伪指令子程序调用伪指令INVOKE与子程序的调用指令CALL在功能上是一致的,但它使汇编语言的子程序调用方法高级语言化,程序员可不用理会一些调用细节问题。调用伪指令INVOKE的使用格式如下:INVOKEexpression[,arguments]其中:expression—地址表达式,通常为子程序名;arguments-传递的各参数之间用逗号',’分开,参数可以是寄存器、表达式或ADDR标识符等。该伪指令是调用基于堆栈的子程序的方法,它把所有参数压栈,子程序结束时,又把参数自动弹出堆栈。在参数传递时,汇编程序将根据子程序的原型进行数据类型检查。若需要进行参数类型转换的话,汇编程序则会自动生成一段代码来满足其数据类型转换的要求。例如:INVOKETEST,AX,12+34,ADDRMSG其中:TEST是子程序名,寄存器AX和表达式“12+34”是参数,“ADDRMSG”是传递变量MSG的地址。例7.6编写一个累加参数数值的子程序。其中参数的个数不定,参数的个数由第一个参数来确定。解:.MODELSMALL.STACK256.CODE;第一个参数parmcount确定其后面参数parmvalues中所含参数的个数ADDUPPROCNEARC,parmcount:WORD,parmvalues:VARARGXORAX,AXXORSI,SIMOVCX,parmcount.REPEATADDAX,parmvalues[SI]ADDSI,2.UNTILCXZRETADDUPENDP.STARTUPINVOKEADDUP,3,5,2,4 ;调用子程序ADDUP,计算5+2+4INVOKEADDUP,4,1,2,3,4 ;调用子程序ADDUP,计算1+2+3+4.EXIT0.END7.5.10局部变量的定义局部变量的定义格式:LOCAL变量名[[数量]][:数据类型][,变量名[[数量]][:数据类型]]...伪指令LOCAL的作用是说明一个或多个临时的局部变量(位于堆栈中)。局部变量必须在任何指令之前加以说明,并可用多个LOCAL伪指令来说明其局部变量。在子程序中,若说明了某个局部变量,则子程序体中的指令就可使用该局部变量。汇编程序会把对它的引用转换成用指针寄存器BP来访问其在堆栈中的实际存储单元。在局部变量的作用域与高级语言中局部变量的作用域相一致,即:局部变量只能在当前子程序中使用,离开该子程序,它们就不能再被引用。但在局部变量的命名规则上有所不同,高级语言中的局部变量可与外层变量同名,而汇编语言中的局部变量不能与其它任何变量同名,否则,在汇编时,将会给出“重定义”(Symbolredefinition)的错误信息。“数量”用来说明该变量所具有的元素个数。象高级语言的数组定义一样,该数量必须写在括号“[]”之中。“数量”说明项是可选项。局部变量的类型说明符可以是任何合法的数据类型说明符。在16位段环境下,该缺省的数据类型是WORD,而在32位段环境下,该缺省的数据类型是DWORD。此处伪指令LOCAL的作用与9.3.1节中伪指令LOCAL的作用是完全不同的,具体的差异请见9.3.1节中的比较。例如:LOCALdata[20]:BYTE,num:WORD在上例的说明中,定义了二个局部变量:data和num。前者是字节类型,并有20个元素,后者是字类型,只有其自身1个元素。7.6子程序库库文件对学过C/C++语言程序设计的读者来说应该是不会陌生的,该语言的程序设计环境提供了大量的库文件,也就是说,提供了大量的标准函数或过程。在本节里,介绍读者如何创建自己的库文件。7.6.1建立库文件命令LIB宏汇编MASM系统提供了建立库文件的命令文件LIB.EXE。其通常是在命令行环境(MS-DOS方式)下使用的,当然,也可在Windows95/98等环境下利用其“开始”菜单下的“运行”功能项来使用。一、 MS-DOS系统显示命令LIB用法的命令如下:•••>lib/?该命令的显示结果如图7.9中所示。二、 Windows系统图7.8运行LIB命令的画面图7.9显示LIB命令功能的画面命令LIB的使用方式和显示结果如图7.8和7.9所示。三、命令显示内容的解释1)、各选项的解释选项含义/?、/HELP显示LIB命令的用法,描述各命令行参数的含义/IGNORECASE/NOIGNORECASE忽略子程序名中的大小写 在实践中,作用不明不忽略子程序名中的大显小写/NOEXTDICTIONARY不建立扩展的目录/NOLOGO不显示版本号和版权信息/PAGESIZE:n设置库文件的每页字节数为n2)、命令项的解释:选项含义+name向库文件中加一个新的目标文件name从库文件中删除一个指定的目标文件+name用新的目标文件替换掉库文件中原有的目标文件*name拷贝出指定的目标文件*name从库文件中移出指定的目标文件在弄懂了LIB的各项功能含义后,读者就可根据自己的需要来建立库文件了。7.6.2建立库文件举例假设现有目标文件subl.obj、sub2.obj和sub3.obj,要用它们建立库文件mylib.lib。可用下列方法来建立该库文件:方法1:所有目标文件都准备好了,可一次性把它们加入到库文件中•••>libmylib+sub1+sub2+sub3方法2:随着目标文件的逐个生成,而依次把它们加入到库文件中•••>libmylib+sub1•••>libmylib+sub2•••>libmylib+sub3假如源文件sub3.asm已修改,并也生成了新的目标文件sub3.obj,这时,就需要把库文件mylib.lib中的sub3.obj替换成新的目标文件。于是,可用下面命令来实现替换:•••>libmylib-+sub3当提示输入目标库文件名(Outputlibrary)时,可按“回车”用默认的原库文件名。如果想查看库文件mylib.lib中各文件的大小和存放的先后次序,可用下列命令:•••>libmylib,list ;把库文件mylib.lib中的文件结构生成到文件list中…〉typelist7.6.3库文件的应用在开发一个功能较弱的应用程序时,其执行文件通常可由一个目标文件连接而成,当开发一个功能较强、关系较复杂的应用程序时,其执行文件很难由一个目标文件连接而成,常常是由多个目标文件(模块)连接而成的。各模块之间无疑会存在着相互调用、相互访问数据单元等内在联系,各模块之间的相互联系就产生了这样的问题:程序员如何在源程序中来表达这种联系?为了解决描述各模块之间的联系,汇编语言提供了二条伪指令PUBLIC和EXTRN,它们的作用有点象C/C++语言说明变量、过程和函数是“全局的”或“外部的”。这二条伪指令的具体用法和含义如下:1、 伪指令PUBLIC伪指令PUBLIC是用来说明:当前模块中哪些标识符是能被其它模块引用的公共标识符。其说明的一般格式如下:PUBLIC标识符1,标识符2,……其中:“标识符”可以是变量名、过程名和程序标号,各标识符之间要用逗号分开。上面说明语句说明了标识符1、标识符2等是公共标识符,可以被其它模块引用。在一个模块中,可用多条PUBLIC伪指令来说明公共标识符。2、 伪指令EXTRN伪指令EXTRN是用来说明:在当前模块所使用的标识符中,哪些标识符是已在其它模块中被定义为指定类型的标识符。如果当前模块使用了其它模块的标识符,而对它又不加以说明的话,那么,在汇编时,汇编程序将会给出下列出错信息:errornnnnn:undefinedsymbol:XXXXXX其中:“nnnnn”是错误号,“XXXXXX”是当前模块中没有定义的标识符。伪指令EXTRN的一般说明格式如下:EXTRN标识符1:类型1,标识符2:类型2,……其中:“标识符”和“类型”之间要用冒号“:”连接。上面语句说明了标识符1、标识符2等是外部标识符,它们在其它模块中已被分别定义为类型1、类型2等,该类型说明符可以是:NEAR、FAR、BYTE、WORD、DWORD等之一。如果在一条说明伪指令中说明了多个标识符,那么,各标识符之间要用逗号分开。在一个模块中,可用多条EXTRN伪指令来说明本模块所引用的外部标识符。注意:伪指令EXTRN中所说明的标识符必须在其定义的模块中被PUBLIC伪指令说明为公共标识符,并且其说明的标识符类型要与该标识符在定义是的类型相一致,否则,要么不能生成其可执行文件,要么其执行文件不能正确运行。例7.7把例7.3、7.4和7.5合并在一起生成一个可执行文件,假设它们所对应的源程序名分别为Count.ASM、DispBX.ASM和Main.ASM。解:由于在源文件Count.ASM中调用了子程序UPPER,所以,例7.1的程序也必须加入到本题中。假设其源文件名为Upper.ASM。由于生成本题的执行文件需要四个模块,模块之间存在着调用关系,所以,在有关源文件中需要说明某些标识符为外部属性,或说明其为公共属性。为了把前面例子中的子程序改写成可汇编的程序,需要添加一些简单的说明语句或进行简单修改,其添加或改写的部分已在下面用“下划线”表示出来。;源文件Upper.ASM;子程序说明信息:……PUBLICUPPERSegUprSEGMENT'code'UPPERPROCFAR ;例7.1中的程序段,在此从略UPPERENDPSegUprENDSEND;源文件DispBX.ASM;子程序说明信息:……PUBLICDISPBXSubDataSEGMENTDB5DUP('0'),0ah,0dh,'$'SubDataENDSSegDispSEGMENT'code'DISPBXPROCFAR ;例7.3中的程序段,在此从略DISPBXENDPSegDispENDSEND;源文件Count.ASM;子程序说明信息:……PUBLICCOUNTEXTRNUPPER:FARSegCountSEGMENT'code'COUNTPROCFAR ;例7.4中的程序段,在此从略COUNTENDPSegCountENDSEND;源文件Main.ASMEXTRNCOUNT:FAR,DISPBX:FAR.MODELSMALL.DATASTRDB'KSDJL0984/[]3oiuOIUOIU(*&(5341',0.CODE.STARTUPLEADX,STRCALLCOUNT ;调用子程序统计出各类字符的个数CALLDISPBX ;调用子程序显示数字字符的个数MOVBX,CXCALLDISPBX ;调用子程序显示字母的个数MOVBX,DICALLDISPBX ;调用子程序显示其它字符的个数.EXIT0END经过以上改写后,可用下列命令把它们分别汇编成目标文件(假设已安装了MASM编程环境):…〉MASMupper…〉MASMdispbx…〉MASMcount…〉MASMmain有了这些目标文件后,可用以下二种方法来生成可执行文件。方法1:把所有的目标文件连接在一起•••>linkmain+upper+count+dispbx方法2:把目标文件upper.obj、count.obj和dispbx.obj加到自己开发的库文件中,然后在连接时,与该库文件连接。•••>libmylib+upper+count+dispbx•••>linkmainMicrosoft(R)SegmentedExecutableLinkerVersion5.31.009Jul131992Copyright(C)MicrosoftCorp1984-1992.Allrightsreserved.RunFile[main.exe]:ListFile[nul.map]:Libraries[.lib]:mylib ;输入要连接的库文件,可用加号“+”连接多个库文件DefinitionsFile[nul.def]:LINK:warningL4021:nostacksegment…〉main ;运行生成
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 拖拉机耕田操作课程设计
- 少儿编程课程设计教案
- 创新驱动用汇报推动企业创新发展案例分享
- 幼儿园春节课程设计
- 思政教育课程设计
- 人工智能发展及其应用前景
- 神经外科手术室感染防控
- 2023年医疗用品制造项目筹资方案
- 2023年磁共振成像装置项目筹资方案
- 中国旅游业的市场现状与发展趋势
- 九年级安全班会课件
- 教研组长培训会议
- 学前儿童卫生与保健-期末大作业:案例分析-国开-参考资料
- 滨州电动伸缩雨棚施工方案
- 24年国开建筑工程估价实训报告
- 医院消防系统维护保养服务投标方案(图文版)(技术方案)
- 花都区2023-2024年-2024年八年级上学期语文期末试卷
- 第七单元 课题1 燃料的燃烧(第一课时)九年级化学上册课件(人教版2024)
- 2025年健康素养知识竞赛题库(含答案)
- 学校食堂供货商合同的退出机制
- 人教版八年级英语上册第五单元教学设计(教案)
评论
0/150
提交评论