资源描述
,第二级,第三级,第四级,第五级,第14章 模块化程序设计,第14章 模块化程序设计,14.1 段间调用,14.2 定义外部标识符伪指令,14.3 使用,EXTRN,和,PUBLIC,的范例,14.4 在指令段使用,PUBLIC,14.5,在数据段使用,PUBLIC,14.6,参数传送,14.7,C,语言与汇编语言的链接,14.1,段,间,调,用,前面介绍的,CALL,指令都是段内的调用,即在同一个指令段内。段内调用的,CALL,指令范围为0000,HFFFFH。,一条段内调用指令(,CALL),的目的码是3个字节长度,例如:,E8 2000(0020);,十六进制,十六进制,E8,是段内调用指令(,CALL),机器指令的操作码,其操作是先把当前,IP,指令指针寄存器的值压入堆栈保存,这个值是,CALL,的下一条指令地址;然后,再把被调用的子程序的偏移地址(2000逆序)值送入,IP,寄存器,,IP0020。,微处理器把当前,CS,的值和,IP,的值相结合形成物理地址,此地址指向被调用子程序的第1字节。当子程序的执行中,遇到,RET,指令离开子程序返回时,,RET,指令会从堆栈中弹出,IP,的保留值,并把它装入,IP,,使程序返回到,CALL,的下一条指令继续执行,这个过程是段内调用。,其特点是在子程序调用、返回过程中段寄存器,CS,不变化,只有指令指针寄存器,IP,发生变化。主程序与被调用的子程序同在一个段内。,模块化程序设计必然涉及到模块间的调用问题。模块间的调用是通过段间的调用来实现的。若被调用的子程序是在现指令段之外,则称为段间调用。一条段间调用指令(,CALL),的机器指令码共有5个字节。例如:,9,A 0002 AF04(04AF 0200);,十六进制,十六进制9,A,是段间调用指令(,CALL),机器指令的操作码。操作是:首先将,CS,段寄存器的值压入堆栈,并把被调用子程序所在段的段值(,AF04,逆序)装入,CS,段寄存器,,CS04AF;,然后把,IP,指令指针寄存器的值压入堆栈,并将被调用子程序相应的偏移地址(0002逆序)装入,IP,IP0200。,这些操作建立了被调用子程序的第一条待执行指令的地址:,十六进制,段 值:,CS 04AF0,偏移地址:,IP,+,0200,物理地址:04,CF0,当离开子程序返回时,段间调用的,RET,指令会从堆栈中依序弹出,IP,和,CS,两个寄存器的原值,返回到,CALL,的下一条指令。其特点是在子程序调用、返回过程中,段寄存器,CS,和指令指针寄存器,IP,均发生变化。主程序与被调用的子程序不在同一个段内。,14.2,定义外部标识符伪指令,当进行模块化程序设计时,首先应考虑的问题是模块间控制的耦合和数据的耦合。控制耦合就是模块在怎样的环境下如何进入又如何退出。数据耦合就是诸模块间如何进行数据通讯。例如,有一个主模块(,MAINPROG),调用一个子模块(,SUBPROG),,它要用到一个段间调用(,CALL),,如图14-1所示。,EXTRN SUBPROG:FAR,MAINPROG:,CALL SUBPROG,PUBLIC SUBPROG,SUBPROG:,RET,图14-1 段间调用,主模块,MAINPROG,内的,CALL,指令,必须知道子模块,SUBPROG,是位于本段之外的标号。否则汇编过程中会产生一个错误信息指出,SUBPROG,是一个未定义的符号。,EXTRN,伪指令就是执行此功能的,它告诉汇编程序,SUBPROG,是一个远程的标号(,FAR Label),,是定义在另一个模块里的。因为汇编程序无法知道真是如此,所以就产生“空的”目的操作数0000,即先空出;而由链接程序在链接时再填入确定值。例如(参考例14.4主模块程序清单):,0011 9,A0000-E,子模块,SUBPROG,内含有一个,PUBLIC,伪指令,它告诉汇编程序和链接程序,其他模块需要知道,SUBPROG,的地址。当,MAINPROG,与,SUBPROG,都已汇编成目的模块文件后,它们可以下列的方式来链接:,LINK MAINPROG+SUBPROG,Run File MAINPROG.EXE:,List File NUL.MAP:CON,Libraries.LIB:,链接程序将一个目的模块内的,EXTRN,匹配上另一个模块内的,PUBLIC,,并将插入所有需要的偏移地址,然后把两个目的模块组合成一个可执行的文件。若有不匹配的情况,链接程序会给出错误信息。,利用,EXTRN,和,PUBLIC,这两条伪指令,一个模块可以访问其他模块的标识符(变量或者标号)。如果一个标识符只在这一个模块中定义过,那么它相对这个模块就是一个内部的标识符或局部标识符。如果它没有在这一个模块中定义过,而是在其他一个模块中定义过,那么它相对于该模块就称为外部标识符。,对于只产生一个单一目标模块的汇编语言程序而言,它所访问的所有标识符必须是局部(内部)定义的,否则就会产生一个错误信息汇编程序会查出有一个未定义的标识符(标号或变量)存在。对于多模块程序来说,必须给汇编程序一个信息以说明其间的有些标识符是外部的,而不至于汇编程序把它们理解为一些无效的标识符。此外为了允许其他模块访问本给定模块中的标识符,该给定模块必须包含一个标识符清单,以说明其中的标识符可以让其他模块访问。因此,每个模块可含有(不一定绝对含有)两个清单,一个清单表明它所访问其他模块的外部标识符(,EXTRN),,而另一个则列出它所定义的且让其他模块访问的标识符(,PUBLIC);,这两个清单靠,EXTRN,和,PUBLIC,这两条伪指令来列出。,EXTRN,和,PUBLIC,伪指令的格式如下:,EXTRN,标识符:类型,PUBLIC,标识符,,EXTRN,伪指令里的标识符是被申明的外部的变量或标号,而,PUBLIC,伪指令里的标识符是供其他模块使用的变量或标号。由于在产生相应的机器代码之前,汇编语言必须要知道所有标识符的类型,以便确定指令的字节数(长度),故在,EXTRN,伪指令里的每一个标识符都伴有类型符出现。,对于变量来说,类型有,BYTE、WORD、DWORD,等,对于标号来说类型则有,NEAR,或,FAR。,注意:标识符可以是变量、符号常量、标号和过程名。,例如:,INC VAR1,语句,如果,VAR1,是外部变量,并且是一字变量,那么在含有这个语句的模块中必须使用下列伪指令:,EXTRN ,VAR1:WORD,而在定义,VAR1,的模块中则一定要有下面的伪指令:,PUBLIC ,VAR1,链接程序的主要任务之一,就是检测并证实,EXTRN,语句里的每一个标识符是否与,PUBLIC,语句的标识符相匹配,如果不相匹配,链接程序就会给出出错信息。下面给出三个模块,说明链接程序是怎样查找匹配,并指出其中错误。,模块1:,EXTRN VAR2:WORD,LAB2:FAR,PUBLIC VAR1,LAB1,;,DATA1 SEGMENT,VAR1 DB 2,VAR3 DB?,VAR4 DW?,DATA1 ENDS,;,LAB1:,模块2:,EXTRN VAR1:BYTE,VAR4:WORD,PUBLIC VAR2,链接错误,原因是模块1中,PUBLIC,;,没有申明匹配,DATA2 SEGMENT,VAR2 DW 0,VAR3 DB 5 DUP(?),DATA2 ENDS,;,模块3:,EXTRN LAB1:FAR,PUBLIC LAB2,LAB3,;,其他模块未使用,LAB2:,不需申明,LAB3:,14.3,使用,EXTRN,和,PUBLIC,的范例,下面的例子中含有两个模块:主模块,CALLMUL1,和一个子模块,SUBMUL1。,主模块定义了堆栈段、数据段和指令段。数据段定义了两个数据项,PRICE,和,QTY。,指令段分别把,PRICE,和,QTY,装入,AX,和,BX,寄存器,然后调用子模块。主模块内的伪指令,EXTRN,指明了本模块使用的外部模块,SUBMUL1。,子模块内有一条伪指令,PUBLIC,,它告诉链接程序,SUMUL1,是由其他模块调用的,同时指明此处是链接的进入点。子模块的功能是把,AX,寄存器的内容乘以,BX,寄存器的内容,所得乘积放入,DX:AX,这一对寄存器内。,子模块内没有定义任何数据,所以它没有数据段;也可以定义数据段,但只能在子模块中使用。,在子模块内也没有定义堆栈段,它与主模块共同使用一个堆栈。所以,在主模块中定义的堆栈,子模块也可以使用。链接程序要求至少有一个堆栈段,在主模块内定义的堆栈段就可以满足要求。,例,14.1,EXTRN,和,PUBLIC,应用。,主模块程序清单如下:,;,主模块:,;,filename:CALLMUL1.ASM,EXTRN SUBMUL1:FAR,;,STACK SEGMENT PARA STACK,STACK,DW 64 DUP (?),STACK ENDS,;,DATASGSEGMENT PARA DATA,QTY DW 0140H,PRICE DW 2500H,DATASGENDS,;,CODESG SEGMENT PARA CODE,BEGIN PROC FAR,ASSUME CS:CODESG,DS:DATASG,SS:STACK,PUSH DS,SUB AX,AX,PUSH AX,MOVAX,DATASG,MOV DS,AX,MOV AX,PRICE,MOV BX,QTY,CALL SUBMUL1,RET,BEGIN ENDP,CODESG ENDS,END BEGIN,子模块程序清单如下:,;,filename:SUBMUL1.ASM,;,子模块:,CODESG SEGMENT PARA CODE,SUBMUL1 PROC FAR,ASSUME CS:CODESG,PUBLIC SUBMUL1,MUL BX,RET,SUBMUL1 ENDP,CODESG ENDS,END SUBMUL1,主模块和子模块分别汇编正确无误后,参考上节内容链接,,产生一个,EXE,文件。,C:,masm,LINK CALLMUL1+SUBMUL1,Run File CALLMUL1.EXE:Enter(,回车,),List File NUL.MAP:Enter,Libraries.LIB:Enter,14.4,在指令段使用,PUBLIC,在主模块的指令段和子模块的指令段使用,PUBLIC,伪指令,链接程序会把两个逻辑指令区段结合成一个实际的指令段。在例,14.1,的主模块和子模块中各有一处修改,均是在指令段的,SEGMENT,伪指令内使用,PUBLIC,,,用法如下,:,COD ESG SEGMENT PARA PUBLIC CODE,例,14.2,在指令段使用,PUBLIC,。,主模块程序清单如下:,;,主模块,:,;,filename:CALLMUL.ASM,EXTRN SUBMUL:FAR,;,STACK SEGMENT PARA STACK STACK,DW 64 DUP (?),STACK ENDS,;,DATASGSEGMENT PARA DATA,QTY DW 0140H,PRICE DW 2500H,DATASG ENDS,;,CODESG SEGMENT PARA PUBLIC CODE,BEGIN PROC FAR,ASSUME CS:CODESG,DS:DATASG,SS:STACK,PUSH DS,SUB AX,AX,PUSH AX,MOV AX,DATASG,MOV DS,AX,MOV AX,PRICE,MOV BX,QTY,CALL SUBMUL,RET,BEGIN ENDP,CODESG ENDS,END BEGIN,子模块程序清单如下:,;,filename:SUBMUL.ASM,;,子模块:,CODESG SEGMENT PARA PUBLIC CODE,SUBMUL PROC FAR,ASSUME CS:CODESG,PUBLIC SUBMUL,MUL BX,RET,SUBMUL ENDP,CODESG ENDS,END SUBMUL,当两个段使用同一个名称,(,CODESG),、,同样的类型,(,CODE,),以及同样段的组合类型,(,PUBLIC),时,链接程序会把这样的两个逻辑段组合成一个实际的物理指令区。通过汇编时产生的,LST,文件中的符号表,可以了解到一个指令段的情况;也可利用,DEBUG,程序观察到一个指令段的情况。,14.5,在数据段使用,PUBLIC,你可能会有这样的需求,在一个模块内要处理另外一个模块的数据。例如前面的例题中,主模块仍然定义数据,PRICE,和,QTY;,但是由子模块使用它们,把它们的值放入,AX,和,BX。,程序修改如下:,例,14.3,在数据段使用,PUBLIC,。,主模块程序清单如下:,;,主模块,:,;,filename:CALLMUL.ASM,EXTRN SUBMUL:FAR,PUBLIC QTY,PRICE,;,STACK SEGMENT PARA STACK STACK,DW 64 DUP (?),STACK ENDS,;,DATASGSEGMENT PARA PUBLIC DATA,QTY DW 0140H,PRICE DW 2500H,DATASGENDS,;,CODESG SEGMENT PARA PUBLIC CODE,BEGIN PROC FAR,ASSUME CS:CODESG,DS:DATASG,SS:STACK,PUSH DS,SUB AX,AX,PUSH AX,MOV AX,DATASG,MOV DS,AX,CALL SUBMUL,RET,BEGIN ENDP,CODESG ENDS,END BEGIN,子模块程序清单如下:,;,filename:SUBMUL.ASM,;,子模块:,EXTRN QTY:WORD,PRICE:WORD,;,CODESG SEGMENT PARA PUBLIC CODE,SUBMUL PROC FAR,ASSUME CS:CODESG,PUBLIC SUBMUL,MOV AX,PRICE,MOV BX,QTY,MUL BX,RET,SUBMUL ENDP,CODESG ENDS,END SUBMUL,PUBLIC QTY,PRICE,;,声明主模块的,QTY,和,PRICE,为外部引用,EXTRN QTY:WORD,PRICE:WORD,;,声明,QTY,和,PRICE,是外部标识符,子模块中使用了主模块的变量,所以两个模块中都应进行声明:,14.6,参,数,传,送,主模块调用子模块,通常也称为主程序调用子程序,主程序经常要传送一些参数给子程序,子程序运行完成后一般都要返回一些信息给主程序。这种主程序和子程序间的信息传送称为参数传送或过程间的数据通信。参数传送的方法有三种:,(1)利用寄存器传递参数:适用参数较少时;,(2)利用存储器传递参数:适用参数较多时;,(3)利用堆栈传递参数:适用嵌套、递归情况。,数据传送根据范围可分为以下几种:,(1)同一个模块内的段内参数传送。,(2)同一个模块内的段间参数传送。,(3)不同模块间的参数传送。,(4)不同语言间的参数传送,例,14.4,利用堆栈传送参数。,主模块程序清单如下:,;,主模块:,;,filename:CALLMUL.ASM,EXTRN SUBMUL:FAR,;,0000STACK SEGMENT PARA STACK STACK,0000 0040 DW 64 DUP (?),?,0080,STACK ENDS,;,0000DATASGSEGMENT PARA DATA,0000 0140QTY DW 0140H,0002 2500PRICE DW 2500H,0004DATASG ENDS,;,0000,CODESG SEGMENT PARA PUBLIC CODE,0000BEGIN PROC FAR,ASSUME CS:CODESG,DS:DATASG,SS:STACK,0000 1EPUSH DS,0001 2B C0 SUB AX,AX,0003 50 PUSH AX,0004 B8,-,RMOV AX,DATASG,0007 8,E D8 MOV DS,AX,0009 FF 36 0002 R PUSH PRICE,000D FF 36 0000 R PUSH QTY,0011 9A 0000,E CALL SUBMUL,0016 CBRET,0017BEGIN ENDP,0017CODESG ENDS,END BEGIN,子模块程序清单如下:,;,filename:SUBMUL.ASM,;,子模块:,0000,CODESG SEGMENT PARA PUBLIC CODE,0000SUBMUL PROC FAR,ASSUME CS:CODESG,PUBLIC SUBMUL,0000 55PUSH BP,0001 8,B ECMOV BP,SP,0003 8B 46 08MOV AX,BP+8,0006 8B 5E 06MOV BX,BP+6,0009 F7 E3MUL BX,000B 5DPOP BP,000C CA 0004RET 4,000FSUBMUL ENDP,000FCODESG ENDS,END,主模块程序在调用子模块程序,SUBMUL,之前,把,PRICE,和,QTY,都压入堆栈。,堆栈的变化如下:,16 00,XX XX 40 01 00 25 00 00 XX XX,6 5 4 3 2 1,(1),PUSH DS,将,DS,中的段地址压入堆栈。,(2),PUSH AX,把偏移地址0压入堆栈。,(3),PUSH PRICE,把2500压入堆栈。,(4),PUSH QTY,把0140压入堆栈。,(5),CALL,把,CS,寄存器的内容压入堆栈。,(6),IP,指令指针寄存器的内容0016 也被压入堆栈。,被调用的子模块程序,SUBMUL,要用,BP,来取得堆栈内的参数。它的第一个操作是把,BP,的内容压入到堆栈保存起来。本例中,BP,的内容正好是0,然后把,SP,的内容送入,BP。,因为,BP,可以作为寻址寄存器,而,SP,则不行。此操作使,BP,的值为0072,因为,SP,的初值应是堆栈的大小十六进制80,每次压入堆栈一个字,SP,减2。堆栈指针的变化应是:,00 00 16 00,XX XX 40 01 00 25 00 00 XX XX .,SP:72 74 76,78 7A,7C,7E 80,因为现在,BP,的内容是0072,所以,PRICE,在,BP+8,位置,而,QTY,在,BP+6,位置。下面两条指令把这些值分别搬入,AX,和,BX,,然后作乘法。要从子模块程序回到调用程序时,先恢复,BP,的值0000并将,SP,加2,从72变成74。,最后一条指令,RET,,是一条子程序返回指令,它执行下列操作:,(1)弹出当前堆栈顶端的字1600送入,IP。,(2),把,SP+2,SP,从74增为76。,(3)取出目前堆栈顶端的字(,XXXX),,存入,CS。,(4),把,SP+2,SP,从76增为78。,子程序返回指令带有参数即,RET 4,,指令中的4的作用是保证正确返回,将,SP,的值加4修正为7,C。,这是因为堆栈内的参数已不再需要,故予以舍弃。使用堆栈传送参数时应特别小心,,SP,的值弄错的话会产生预想不到的结果。,14.7,C,语言与汇编语言的链接,C,语言与汇编语言的链接是指,C,语言与汇编语言的互相调用。通常只是指从,C,语言调用汇编语言程序。这是因为,C,语言不但编程容易,而且效率高。但是,汇编语言效率最高,特别是适合对硬件的直接控制。因此,采用,C,语言与汇编语言混合编程的方法,能得到高质量的程序。,C,语言与汇编语言的连接要注意以下几个问题:,(1)存储器分配问题:通常情况下,存储器分配问题由链接程序解决,用户不必考虑。,(2)两种语言间的控制耦合问题:汇编语言程序一般作为,C,语言的外部过程,由,C,语言通过过程调用汇编语言程序。,(3)参数传送问题:一般用数值或地址的方式传送。,1.,C,语言调用汇编语言举例,例14.5 计算,A(2X。,C,语言程序如下:,extrnint,power(,int,int,),Main(),printf,(3*time 2 of thepowerof5 is%dn,_power(2,5);,汇编语言子程序:,_,TEXTSEGMENT,ASSUMECS:_TEXT,PUBLIC_POWER,_POWERPROC,PUSHBP,MOV BP,SP,MOV AX,BP+4,MOV CX,BP+6,SHLAX,CL,RET,_,POWERENDP,_TEXTENDS,END,2.,C,语言程序嵌入汇编指令举例,例14.6 将键盘输入的数乘2并输出。,第一种方法:用预处理指令#,asm,、#,endasm,。,main(),int,i,j;,char*s;,printf,(pleaseinput i=);,scanf,(%d,i);,#,asm,mov,ax,i,mov cl,2,mul cl,mov,j,ax,#,endasm,printf,(i(2=%d,j);,第二种方法:汇编语句行前加关键字,asm,,,每行结尾按,C,语言规则加分号。,main(),int,i,j;,char*s;,printf,(pleaseinput:i=);,scanf,(%d,i);,asmmov,ax,i,asmmov cl,2,asmmul cl,asmmov,j,ax,printf,(i(2=%d,j);,操作方法:,(1)在,TC,集成环境下输入源程序文件。,(2)把,MASM.EXE,改名为,TASM.EXE,并复制到,TC,目录下。,(3)用,TCC.EXE,编译、链接程序:,TCC-B-C(,库文件路径)文件名库文件名,
展开阅读全文