1、汇编语言程序设计汇编语言程序设计 吴吴 向向 军军中山大学计算机科学系中山大学计算机科学系第第6章章 程序的基本结构程序的基本结构6.1.1 段的定义段的定义 在定义段时,每个段都有一个段名。在取段名时,要取一个具有一定含义的段名。段定义的一般格式如下:段名 SEGMENT 对齐类型 组合类型 类别 ;段内的具体内容段名 ENDS 其中:“段名”必须是一个合法的标识符,前后二个段名要相同。可选项“对齐类型”、“组合类型”和“类别”的说明作用请见6.3节中的叙述。段的长度是指该段所占的字节数:u如果段是数据段,则其长度是其所有变量所占字节数的总和;u如果段是代码段,则其长度是其所有指令所占字节数
2、的总和。在通常情况下,一个段的长度不能超过64K,在80386及其以后系统的保护方式下,段基地址是32位,段的最大长度可达4G。6.1 源程序的基本组成源程序的基本组成第第6章章 程序的基本结构程序的基本结构一个数据段的定义例子:DATA1 SEGMENTword1DW1,9078H,?byte1DB21,WorldDD12345678HDATA1 ENDS一个代码段的例子:CODE1 SEGMENTMOVAX,DATA1;把数据段DATA1的段值送AXMOVDS,AX;把AX的值送给DS,即:DS存储数据段的段值MOVAX,4C00HINT21H;调用DOS功能,结束程序的运行CODE1 E
3、NDS第第6章章 程序的基本结构程序的基本结构6.1.2 段寄存器的说明语句段寄存器的说明语句 在汇编语言源程序中可以定义多个段,每个段都要与一个段寄存器建立一种对应关系。建立这种对应关系的说明语句格式如下:ASSUME 段寄存器名:段名,段寄存器名:段名,其中:段寄存器是CS、DS、ES、SS、FS和GS,段名在段定义语句说明。例如,ASSUME CS:CODE1,DS:DATA1 说明了:CS对应于代码段CODE1,DS对应于数据段DATA1。在ASSUME语句中,还可以用关键字NOTHING来说明某个段寄存器不与任何段相对应。下面语句说明了段寄存器ES不与某段相对应。ASSUME ES:
4、NOTHING 在通常情况下,代码段的第一条语句就是用ASSUME语句来说明段寄存器与段之间的对应关系。在代码段的其它位置,还可以用另一个ASSUME语句来改变前面ASSUME语句所说明的对应关系,这样,代码段中的指令就用最近的ASSUME语句所建立的对应关系来确定指令中的有关信息。第第6章章 程序的基本结构程序的基本结构例6.1:汇编语言段及其段说明语句的作用。DATA1SEGMENT;定义数据段DATA1word1DW5678hbyte1DB“ABCDEFG”DATA1ENDSDATA2SEGMENT;定义数据段DATA2word2DW1234hword3DW9876hDATA2ENDSD
5、ATA3SEGMENT;定义数据段DATA3byte2DB?DATA3ENDS第第6章章 程序的基本结构程序的基本结构CODE1 SEGMENT;编写代码段CODE1ASSUME CS:CODE1,DS:DATA1,ES:DATA2;MOVAX,DATA1;MOVDS,AX;MOVAX,DATA2;MOVES,AX;MOVAX,word1;访问段DATA1中的字变量word1MOVword2,AX;访问段DATA2中的字变量word2ASSUME DS:DATA3,ES:NOTHING;MOVAX,DATA3MOVDS,AXMOVBL,byte2;访问段DATA3中的变量byte2MOVAX,
6、4C00H;INT21H;CODE1 ENDS第第6章章 程序的基本结构程序的基本结构6.1.3 堆栈段的说明堆栈段的说明在源程序中,可用以下方法来定义堆栈段。方法1:Stack1SEGMENTDB256 DUP(?);256是堆栈的长度,可根据需要进行改变TOPLABEL WORDStack1ENDS 由于堆栈是按地址从大到小的存储单元顺序来存放内容的,所以,在堆栈存储单元说明语句之后,再说明一个栈顶别名,这样,对SP的赋值就很方便。在源程序中,还要添加如下程序段,才能把段Stack1当作堆栈段来使用。ASSUME SS:STACK1;可在代码段的段指定语句中一起说明CLI;禁止响应可屏蔽中
7、断MOVAX,STACK1MOVSS,AXMOVSP,offset TOP;给堆栈段的栈顶寄存器SP赋初值STI;恢复响应可屏蔽中断第第6章章 程序的基本结构程序的基本结构方法2:STACK1SEGMENT STACK;定义一个堆栈段,其段名为STACK1DB256 DUP(?)STACK1ENDS 上述段定义说明了该段是堆栈段,系统会自动把段寄存器SS和栈顶寄存器SP与该堆栈段之间建立相应的关系,并设置其初值,而不用在代码段对它们进行赋值。除了以上二种方法外,还有一种更简洁的方法来定义堆栈段,有关内容请见第6.4.2节中的叙述。第第6章章 程序的基本结构程序的基本结构6.1.4 源程序的结构
8、源程序的结构例6.2:在屏幕上显示字符串“Hello,World.”STACK1SEGMENT STACK;定义堆栈段STACK1DB 256 DUP(?)STACK1ENDSDATA1SEGMENT;定义数据段DATA1MSGDB“Hello,World.$”DATA1ENDSCODE1 SEGMENT;编写代码段CODE1ASSUME CS:CODE1,DS:DATA1START:MOVAX,DATA1MOVDS,AXMOVDX,offset MSGMOVAH,9INT21H;中断21H的9H号功能,显示DS:DX指向的字符串MOVAX,4C00HINT21HCODE1 ENDSENDST
9、ART;源程序的结束语句 伪指令END表示源程序到此为止,汇编程序对该语句之后的任何内容都不作处理,所以,通常情况下,伪指令END是源程序的最后一条语句。伪指令END后面可附带一个在程序中已定义的标号,该标号指明程序的启动位置。如果源程序是一个独立的程序或主模块,那么,伪指令END后面一定要附带一个标号;如果源程序仅是一个普通模块,那么,其END后面就一定不能附带标号。第第6章章 程序的基本结构程序的基本结构6.2.1 顺序结构顺序结构在学习高级语言程序设计时,我们知道了程序的三大主要结构:顺序结构、分支结构和循环结构。在汇编语言的源程序也同样有此三大结构,所不同的是它们的表现形式不同。用高级
10、语言编写程序时,由于不使用“转移语句”而使这三种结构清晰明了。6.2 程序的基本结构程序的基本结构 顺序结构是最简单的程序结构,程序的执行顺序就是指令的编写顺序,所以,安排指令的先后次序就显得至关重要。另外,在编程序时,还要妥善保存已得到的处理结果,为后面的进一步处理直接提供前面的处理结果,从而避免不必要的重复操作。例6.3 编写程序段,完成下面公式的计算。A(X-Y+24)/Z的商,B(X-Y+24)/Z的余数其中:变量X和Y是32位有符号数,变量A,B和Z是16位有符号数。第第6章章 程序的基本结构程序的基本结构6.2.2 分支结构分支结构例6.5 已知字节变量CHAR1,编写一程序段,把
11、它由小写字母变成大写字母。例6.6 编写一程序段,计算下列函数值。其中:变量X和Y是有符号字变量。例6.7 把下列C语言的语句改写成等价的汇编语言程序段(不考虑运算中的溢出)。If(a+b 0&c%2=0)a=62;else a=21;其中:变量a、b和c都是有符号的整型(int)变量。第第6章章 程序的基本结构程序的基本结构分支伪指令的具体格式如下:格式1:.IF condition 指令序列.ENDIF格式2:.IF condition 指令序列1.ELSE 指令序列2.ENDIF格式3:.IF condition1 指令序列1.ELSEIF condition2 指令序列2.ENDIF
12、其中:条件表达式“condition”的书写方式与C语言中条件表达式的书写方式相似,也可用括号来组成复杂的条件表达式。第第6章章 程序的基本结构程序的基本结构 条件表达式中可用的操作符有:=(等于)、!=(不等)、(大于)、=(大于等于)、(小于)、=(小于等于)、&(位操作与)、!(逻辑非)、&(逻辑与)、|(逻辑或)等。若在条件表达式中检测标志位的信息,则可以使用的符号名有:CARRY?(相当于CF=1)、OVERFLOW?(OF=1)、PARITY?(PF=1)、SIGN?(SF=1)、ZERO?(ZF=1)等。例如:.IF CARRY?&AX!=BX;检测CF=1且AX!=BX是否成立
13、;汇编语言指令序列.ENDIF 在指令序列中,还可再含有其它的.IF伪指令,即:允许嵌套。伪指令.ELSEIF引导出另一个二叉分支,但它不能作伪指令块的第一个伪指令。第第6章章 程序的基本结构程序的基本结构6.2.3 循环结构循环结构 循环结构是一个重要的程序结构,它具有重复执行某段程序的功能。通常,循环结构包括以下四个组成部分:u循环初始化部分初始化循环控制变量、循环体所用到变量;u循环体部分循环结构的主体;u循环调整部分循环控制变量的修改、或循环终止条件的检查;u循环控制部分程序执行的控制转移。第第6章章 程序的基本结构程序的基本结构一、用循环指令构成循环结构例6.10:分类统计字数组da
14、ta中正数、负数和零的个数,并分别存入内存字变量Positive、Negative和Zero中,数组元素个数保存在其第一个字中。例6.11:把数组score的平均值(取整)存入字变量Average中,数组以负数为结束标志。第第6章章 程序的基本结构程序的基本结构二、用伪指令实现的循环结构 在宏汇编MASM 6.11系统中,增加了表达循环结构的伪指令:WHILE循环、REPEAT-UNTIL循环。另外,还增加两个辅助循环的伪指令。循环伪指令的格式和含义如下:1、WHILE型循环伪指令.WHILE condition 循环体的指令序列;条件condition”成立时所执行的指令序列.ENDW其中:
15、.ENDW与前面的.WHILE相匹配,它标志着其循环体到此结束。如果条件表达式“condition”在循环开始时,就为“假”(false),那么,该循环体一次也不会被执行。第第6章章 程序的基本结构程序的基本结构2、REPEAT型循环伪指令.REPEAT.REPEAT 循环体的指令序列 循环体的指令序列.UNTIL condition.UNTILCXZ condition REPEAT型循环在执行完循环体后,才判定逻辑表达式condition的值。若该表达式的值为真,则终止该循环,并将执行伪指令.UNTILCXZ后面的指令,否则,将向上跳转到伪指令.REPEAT之后的指令,为继续执行其循环体作
16、准备。如果.UNTILCXZ后面没有写逻辑表达式,那么,由.REPEAT.UNTILCXZ所构成的循环与用LOOP指令所过程的循环是一致的,它们都是以“CX=0”为循环终止条件。如果.UNTILCXZ后面书写了逻辑表达式,那么,该逻辑表达式的形式只能是:“EXP1=EXP2”或“EXP1!=EXP2”。所以,这时由“.REPEAT.UNTILCXZ condition”所构成的循环就与用LOOPNE/LOOPE指令所过程的循环是一致的,它们都是以“condition|CX=0”为循环终止条件。.REPEAT.UNTILCXZ的循环体也会至少被执行一次。.WHILE.ENDW和.REPEAT.U
17、NTILCXZ的循环体内还可再含有循环伪指令,这样就构成了循环结构的嵌套。第第6章章 程序的基本结构程序的基本结构3、辅助循环伪指令n终止循环伪指令.BREAK.BREAK .IF condition 该伪指令用来终止包含它的最内层循环。前者是无条件终止循环,后者是仅当逻辑表达式condition为真时,才终止循环。.WHILE 1.REPEAT .BREAK.IF condition .BREAK.IF condition .ENDW.UNTIL 0 对于以上二个循环,如果没有指令来终止循环的话,它们都将进入死循环状态,但如果在该层循环体内,存在伪指令“.BREAK.IF condition
18、”的话,那么,当逻辑表达式condition为真时,该循环就会被终止了。第第6章章 程序的基本结构程序的基本结构n循环继续伪指令.CONTINUE.CONTINUE .IF condition 该伪指令用于直接跳转到包含它的最内层循环的计算循环条件表达式的代码处。前者是无条件转移到计算循环条件表达式的代码处,后者是仅当条件表达式condition为真时,才进行这样的跳转。辅助循环伪指令.BREAK和.CONTINUE只能在伪指令.WHILE.ENDW和.REPEAT.UNTIL的循环体内使用。例6.12 显示9个数字字母19,26个大写字母,和显示任意输入的数字字符,并用按“回车”键来结束本程
19、序的运行。第第6章章 程序的基本结构程序的基本结构 段定义的一般格式如下:段名 SEGMENT 对齐类型 组合类型 类别 段名 ENDS 段属性“对齐类型”、“组合类型”和“类别”要按此顺序说明,但这些选项可根据需要选择书写。如果源程序中不指定某个属性,那么,汇编程序将使用该属性的缺省值。程序中的段名可以是唯一的,也可以与其它段同名。在同一模块中,如果有二个段同名,则后者被认为是前段的后续,这样,它们就属同一段。当同一模块出现二个同名段时,则后者的可选项属性要么与前者相同,要么不写其属性而选用前者的段属性。略。6.3 段的基本属性段的基本属性第第6章章 程序的基本结构程序的基本结构程序存储模式
20、说明伪指令的格式如下:.MODEL 存储模式,语言类型,操作系统类型,堆栈类型 程序可选的存储模式有:TINY、SMALL、COMPACT、MEDIUM、LARGE、HUGE和FLAT。伪指令.MODEL必须写在源程序的首部,且只能出现一次,其前内容只能是注释。如果用伪指令来指定程序所遵循的语言类型,那么,将不允许子程序的嵌套定义。6.4 简化的段定义简化的段定义6.4.1 存储模型说明伪指令存储模型说明伪指令第第6章章 程序的基本结构程序的基本结构lTINY 该存储类型是为编写COM文件类型而设置的。程序员还可用汇编命令行选项/AT和连接命令选项/TINY来达到此目的。lSMALL 所有的数
21、据变量必须在一个数据段之内,所有的代码也必须在一个代码段之内。该存储类型是独立汇编语言源程序常用的存储模型。lMEDIUM 所有的数据变量必须在一个数据段之内,但代码段可以有多个。在这种模型下,数据段寄存器的内容保持不变,转移可以是段间转移。lCOMPACT 数据段可以有多个,但代码段只能有一。lLARGE 数据段和代码段都可以有多个,但一个数组的字节数不能超过64KB。lHUGE 数据段和代码段都可以有多个,一个数组的字节数也可以超过64KB。lFLAT FLAT存储模式在创建执行文件时,将使该程序仅含一个包括程序数据和代码的32位段,并且只能在80386及其以后的计算机系统中运行。该程序的
22、文件类型为EXE。第第6章章 程序的基本结构程序的基本结构程序存储模式说明伪指令的格式如下:.MODEL 存储模式,语言类型,操作系统类型,堆栈类型 程序可选的存储模式有:TINY、SMALL、COMPACT、MEDIUM、LARGE、HUGE和FLAT。伪指令.MODEL必须写在源程序的首部,且只能出现一次,其前内容只能是注释。如果用伪指令来指定程序所遵循的语言类型,那么,将不允许子程序的嵌套定义。6.4.2 简化段定义伪指令简化段定义伪指令第第6章章 程序的基本结构程序的基本结构u代码段定义.CODE 作用:说明其下面的内容是代码段中内容。u堆栈段定义.STACK 堆栈字节数 其中,“堆栈
23、字节数”可以不写,其缺省值为1024B。u数据段定义.DATA/.DATA?/.CONST 作用:说明其下面的内容是数据段中的变量定义。在一个源程序中,可以有多个伪指令“.DATA”定义的数据段,这就好象在源程序中定义多个同段名的数据段一样。伪指令“.DATA?”说明下面是一个未初始化数据段的开始,伪指令“.CONST”说明下面是一个常数数据段的开始。u远程数据段定义.FARDATA 段名/.FARDATA?段名 其中:“段名”是可选项,如果不指定的话,则该段名就取其缺省段名。作用:说明一个独立的数据段。伪指令“.FARDATA?”说明下面是一个未初始化的、独立数据段的开始。第第6章章 程序的
24、基本结构程序的基本结构表6.3 小模式下简化段定义的缺省属性表6.4.3 简化段段名的引用简化段段名的引用伪指令缺省段名对齐类型组合类型类别段组名.CODE_TEXTWORDPUBLICCODE.FARDATAFAR_DATAPARANONEFAR_DATA.FARDATA?FAR_BSSPARANONEFAR_BSS.STACKSTACKPARASTACKSTACKDGROUP.DATADATAWORDPUBLICDATADGROUP.DATA?BSSWORDPUBLICBSSDGROUP.CONSTCONSTWORDPUBLICCONSTDGROUP第第6章章 程序的基本结构程序的基本结构
25、 在汇编程序MASM中,提供了二组简化的代码伪指令:.STARTUP和.EXIT。.STARTUP在代码段的开始用于自动初始化寄存器DS、SS和SP;.EXIT用于结束程序的运行,它等价于下列二条语句:MOVAH,4CHINT21h 当使用汇编程序TASM时,以上二条伪指令分别改为:STARTUPCODE和EXITCODE。例6.16:.MODEL SMALL.STACK 128.DATA MSG DBSimplified Segment Directives.$.CODE .STARTUP;自动初始化寄存器DS、SS和SP MOVDX,offset MSG MOVAH,9H INT21h .
26、EXIT 0 END第第7章章 子程序和库子程序和库 定义子程序的一般格式如下:子程序名 PROC NEAR|FAR ;子程序体子程序名 ENDP 对子程序定义的具体规定如下:7.1 子程序的定义子程序的定义u“子程序名”必须是一个合法的标识符,并前后二者要一致;uPROC和ENDP必须是成对出现的关键字,表示子程序定义开始和结束;u子程序的类型有近(NEAR)、远(FAR)之分,其缺省的类型是近类型;uNEAR类型的子程序只能被与其同段的程序所调用,FAR类型的子程序可被不同段的程序所调用;u子程序至少要有一条返回指令。返回指令是子程序的出口语句,但它不一定是子程序的最后一条语句;u子程序名
27、有三个属性:段值、偏移量和类型。其段值和偏移量对应于子程序的入口地址,其类型就是该子程序的类型。第第7章章 子程序和库子程序和库 在编写子程序时,除了要考虑实现子程序功能的方法,还要养成书写子程序说明信息的好习惯。其说明信息一般包括以下几方面内容:u功能描述u入口和出口参数u所用寄存器;可选项,最好采用寄存器的保护和恢复方法u所用额外存储单元;可选项,可以减少为子程序定义自己的局部变量u子程序的所采用的算法;可选项,如果算法简单,可以不写u调用时的注意事项;可选项,尽量避免除入口参数外还有其它的要求u子程序的编写者;可选项,为将来的维护提供信息u子程序的编写日期;可选项,用于确定程序是否是最新
28、版本 这些说明性信息虽然不是子程序功能的一部分,但其他程序员可通过它们对该子程序的整体信息有一个较清晰认识,为准确地调用它们提供直接的帮助,与此同时,也为实现子程序的共享提供了必要的资料。第第7章章 子程序和库子程序和库 调用子程序指令格式如下:CALL 子程序名/Reg/Mem 子程序的调用指令分为近(near)调用和远(far)调用。如果被调用子程序的属性是近的,那么,CALL指令将产生一个近调用,它把该指令之后地址的偏移量(用一个字来表示的)压栈,把被调用子程序入口地址的偏移量送给指令指针寄存器IP即可实现执行程序的转移。7.2 子程序的调用和返回指令子程序的调用和返回指令7.2.1 调
29、用指令调用指令第第7章章 子程序和库子程序和库如果被调用子程序的属性是远的,那么,CALL指令将产生一个远调用。这时,调用指令不仅要把该指令之后地址的偏移量压进栈,而且也要把段寄存器CS的值压进栈。在此之后,再把被调用子程序入口地址的偏移量和段值分别送给IP和CS,这样完成了子程序的远调用操作。例如:CALL DISPLAY;DISPLAY是子程序名 CALL BX;BX的内容是子程序的偏移量 CALL WORD1;WORD1是内存字变量,其值是子程序的偏移量 CALL DWORD1;DWORD1是双字变量,其值是子程序偏移量和段值 CALL word ptr BX;BX所指内存字单元的值是子
30、程序的偏移量 CALL dword ptr BX;BX所指内存双字单元的值是子程序的偏移量和段值第第7章章 子程序和库子程序和库 当子程序执行完时,需要返回到调用它的程序之中。为了实现此功能,指令系统提供了一条专用的子程序返回指令。其格式如下:RET/RETN/RETF Imm 子程序的返回在功能上是子程序调用的逆操作。为了与子程序的远、近调用相对应,子程序的返回也分:远返回和近返回。7.2.2 返回指令返回指令第第7章章 子程序和库子程序和库 如果返回指令后面带有立即数(其值通常为偶数),则表示在得到返回地址之后,SP还要增加的偏移量,它不是类似于高级语言中子程序的返回值。例如:RET;可能
31、是近返回,也可能是远返回RETN;近返回指令RETF;远返回指令RET 6;子程序返回后,(SP)(SP)+6第第7章章 子程序和库子程序和库例7.1:编写一个子程序UPPER,实现把寄存器AL中存放的字符变大写。解:;子程序功能:把AL中存放的字符变大写;入口参数:AL;出口参数:AL;算法描述:判断AL中字符必须在az之间才能把该字符变为大写UPPER PROCCMPAL,a;书写a的ASCII码61H也可以JBoverCMPAL,zJAoverSUBAL,20H;书写指令AND AL,0DFH也可以over:RETUPPER ENDP例7.2:编写一个求字符串长度的子程序StrLen,该
32、字符串以0为结束标志,其首地址存放在DS:DX,其长度保存在CX中返回。解:;子程序功能:求字符串的长度;入口参数:DS:DX存放字符串的首地址,该字符串以0为结束标志;出口参数:CX存放该字符串的长度;算法描述:用BX来指针来扫描字符串中的字符,如果遇到其结束标 志,则停止扫描字符串操作StrLenPROCPUSHAXPUSHBX;用堆栈来保存子程序所用到的寄存器内容XORCX,CXXORAL,ALMOVBX,DXagain:CMPBX,ALJZoverINCCX;增加字符串的长度INCBX;访问字符串的指针向后移JMPagainover:POPBX;恢复在子程序开始时所保存的寄存器内容PO
33、PAXRETStrLenENDP第第7章章 子程序和库子程序和库 一方面,由于CPU中的寄存器在任何程序中都是“可见”的,一个程序对某寄存器赋值后,在另一个程序中就能直接使用,所以,用寄存器来传递参数最直接、简便,也是最常用的参数传递方式。但另一方面,CPU中寄存器的个数和容量都是非常有限,所以,该方法适用于传递较少的参数信息。例7.1是用寄存器传递参数的例子,子程序处理的数据被保存在寄存器AL中。假设有下列的程序段:MOVAL,bCALLUPPER;子返回时,(AL)=BMOVAL,2CALLUPPER;子返回时,AL的值不变,因为2不是字母7.3 子程序的参数传递子程序的参数传递7.3.1
34、 寄存器传递参数寄存器传递参数第第7章章 子程序和库子程序和库例7.3:按五位十进制的形式显示寄存器BX中的内容,如果BX的值小于0,则应 在显示数值之前显示负号-。例如:(BX)=123,显示:00123;(BX)=-234,显示:-00234;解:见书,略。第第7章章 子程序和库子程序和库 在调用子程序时,当需要向子程序传递大量数据时,因受到寄存器容量的限制,就不能采用寄存器传递参数的方式,而要改用约定存储单元的传送方式。例7.2是采用约定存储单元传递参数的例子,所处理的数据不是直接传给子程序,而是把存储它们的地址告诉子程序。7.3.2 约定存储单元传递参数约定存储单元传递参数例7.4:编
35、写一个子程序分类统计出一个字符串中数字字符、字母和其它字符的个数。该字符串的首地址用DS:DX来指定(以0为字符串结束),各类字符个数分别存放BX、CX和DI中。解:见书,略。例7.5:显示出任意字符串中数字字符、字母和其它字符的个数。解:见书,略。第第7章章 子程序和库子程序和库 堆栈是一个特殊的数据结构,它通常是用来保存程序的返回地址。当用它来传递参数时,势必会造成数据和返回地址混合在一起的局面,用起来要特别仔细。具体做法如下:7.3.3 堆栈传递参数堆栈传递参数u当用堆栈传递入口参数时,要在调用子程序前把有关参数依次压栈,子程序从堆栈中取到入口参数;u当用堆栈传递出口参数时,要在子程序返
36、回前,把有关参数依次压栈(这里还需要做点额外操作,要保证返回地址一定在栈顶),调用程序就可以从堆栈中取到出口参数。第第7章章 子程序和库子程序和库1、用堆栈传递入口参数的调用方法:PUSHPara1PUSHParan;把n个字的参数压栈CALLSUBPRO;调用子程序SUBPRO第第7章章 子程序和库子程序和库1、用堆栈传递入口参数的调用方法:PUSHPara1PUSHParan;把n个字的参数压栈CALLSUBPRO;调用子程序SUBPRO第第7章章 子程序和库子程序和库2、在子程序中取入口参数的方法u段内调用子程序SUB1PROC NEARPUSHBP;保护寄存器BPMOVBP,SP;用寄
37、存器BP来访问堆栈,读取参数;保护其它寄存器的指令MOVParan,BP+4MOVPara1,BP+4+2*(n-1)SUB1ENDP从堆栈中取入口参数第第7章章 子程序和库子程序和库u段间调用子程序 在段间调用子程序时,CALL指令会把返回地址的偏移量和段寄存器CS的内容都压栈。在进入子程序后,需要用BP来读取传递过来的参数,所以,也要先保护BP原来的值,再把当前SP的值传送给BP。当前BP所指向的堆栈单元与最后一个参数Paran之间隔着BP的原值、返回地址的偏移量和段地址,所以,二者之间相差6个字节。第第7章章 子程序和库子程序和库 在子程序中,保存和恢复寄存器内容的主要方法是:在子程序的
38、开始把它所用到的寄存器压进栈,在返回前,再把它们弹出栈。这样编写的好处是该子程序可以被任何其它程序来调用。在调用指令前,不需要保存寄存器,在调用指令后,也无需恢复寄存器。利用堆栈来保存和恢复寄存器内容方法的一般形式如下:XXXXX PROCPUSHREG1;把所使用的寄存器压栈,REGi代表某个寄存器PUSHREGn;子程序的处理功能语句POPREGn;前面压栈的寄存器弹出,注意它们的次序POPREG1RETXXXXX ENDP7.4 寄存器的保护与恢复寄存器的保护与恢复第第7章章 子程序和库子程序和库 在子程序中利用堆栈来保存和恢复寄存器内容。利用堆栈来实现此项功能时,应注意以下几点:u用堆
39、栈保存和恢复寄存器的内容,要注意堆栈“先进后出”的操作特点u通常情况下不保护入口参数寄存器的内容,当然,也可以根据事先的约定而对它们加以保护u如果用寄存器带回子程序的处理结果,那么,这些寄存器一定不能加以保护u整个子程序的执行几乎肯定要改变标志位,可用PUSHF和POPF来保护和恢复标志位,但一般在子程序中不保护标志位,除非有此特殊需要第第7章章 子程序和库子程序和库子程序名 PROC distance langtype visibility USES 寄存器列表,参数:数据类型.LOCAL varlist 子程序的程序体子程序名 ENDP 定义子程序时,可使用参数表来直接指明其所要的参数,但
40、程序员必须先用.MODEL伪指令,或使用参数来说明本子程序所使用的程序设计语言类型。程序员在定义子程序时,最好先说明该子程序的原型(用伪指令PROTO)。这样,在调用时,系统可以自动进行类型检查,也可以使用更方便的调用伪指令INVOKE来调用该子程序。7.5 子程序的完全定义子程序的完全定义7.5.1 子程序完全定义格式子程序完全定义格式第第7章章 子程序和库子程序和库 子程序的位距(Distance)有:Near、Far、Near16、Far16、Near32和Far32。子程序位距描述符告诉汇编程序该子程序是在本段之内(Near),还是在本段之外(Far)。7.5.2 子程序的位距子程序的
41、位距 子程序语言类型(Language Type)可以是任何一种有效的程序设计语句类型,由它来告诉汇编程序将使用什么样的标识符的命名风格、子程序的调用和返回约定。该语言类型说明可使汇编语言程序与其它语言程序达到共享的目的。程序员可用另外三种方法来设置程序的语言类型:.MODEL、OPTION LANGTYPE:和命令行选项/Gx。若在程序和命令行中都说明了语言类型,那么,前者的说明优先。7.5.3 子程序的语言类型子程序的语言类型第第7章章 子程序和库子程序和库 子程序的可见性(Visibility)决定该子程序对其它模块是否可用。它共有三个属性值:PRIVATE、PUBLIC和EXPORT。
42、7.5.4 子程序的可见性子程序的可见性 当程序员想用自己定义的宏来替代缺省的“起始”和“结束”的代码段时,可用下列说明语句来实现:OPTION PROLOGUE:MacroName1OPTION EPILOGUE:MacroName2 PROLOGUE和EPILOGUE分别指定MacroName1和MacroName2为“起始”和“结束”代码段的宏名。若程序员不要汇编程序自动产生“起始”和“结束”代码,则可用NONE来代替说明语句中的宏名,即:OPTION PROLOGUE:NONEOPTION EPILOGUE:NONE7.5.5 子程序的子程序的”起始起始”和和”结束结束”操作操作第第7
43、章章 子程序和库子程序和库 保护寄存器说明子句的说明格式:USES 寄存器列表 该说明子句要求汇编程序为其生成保护和恢复寄存器的指令序列:n在进入子程序执行指令之前,把寄存器列表中的寄存器压进堆栈;n在结束子程序时,把先前压进堆栈的寄存器弹出,以达到保护寄存器的目的。寄存器列表:列举出在子程序中需要保护的寄存器名,即:在子程序开始时需要把内容进栈的寄存器名。若有多个寄存器名,则在寄存器名之间要用“空格”来分开。7.5.6 寄存器的保护和恢复寄存器的保护和恢复第第7章章 子程序和库子程序和库例如:DsipPROC USES AX DX,FUNC:WORD,MSG:PTR BYTEMOVDX,MS
44、GMOVAX,FUNCINT21HRETDispENDP 汇编程序在处理该子程序时,会根据子句USES的作用,在第一条指令“MOV DX,MSG”之前,插入把寄存器AX和DX进栈的指令序列,即:PUSHAXPUSHDX 而在返回指令RET之前插入把寄存器DX和AX的值弹出的指令序列,即:POPDXPOPAX 注意:若子程序含有多个RET或IRET指令,那么,汇编程序在每个RET或IRET指令前都将增加相应的弹出堆栈指令序列。第第7章章 子程序和库子程序和库 子程序参数是用来向子程序传递信息的数据。若有多个参数,则参数之间要用逗号分割。为了能说明子程序的参数,程序员必须事先指定参数所遵循的语言类
45、型或使用“语言类型”参数。参数的数据类型可以是任何一个有效的数据类型说明符或VARARG。VARARG数据类型允许向子程序传递“个数”不定的参数,其参数之间要用逗号“,”来分开。若参数表中含有VARARG说明的参数,那么,该参数一定是该子程序的最后一个参数。其规定隐含地说明了在参数表中只能有一个用VARARG说明的参数。如果没有显式地指定某个参数的数据类型,那么,在16位段规模的情况下,其缺省的数据类型是WORD;在32位段规模的情况下,其缺省的数据类型是DWORD。7.5.7 子程序的参数传递子程序的参数传递第第7章章 子程序和库子程序和库 子程序原型的说明格式如下:子程序名 PROTO d
46、istance langtype,parameter:tag.该说明语句告诉汇编程序该子程序的若干属性,如:位距、语语言类型、参数个数及其类型等。这样,汇编程序就可以对其定义进行适当的检查。如果对所有基于堆栈的过程都定义一个原型,那么,就可把这些原型存放在一个独立的包含文件(用伪指令INCLDUE来装入)中。使用这种方法对将来把所有子程序放入自定义的库文件中是非常方便的。该原型说明语句中参数distance、langtype、parameter和tag等的含义与前面的叙述相一致。7.5.8 子程序的原型说明子程序的原型说明第第7章章 子程序和库子程序和库 子程序调用伪指令INVOKE与调用指令
47、CALL在功能上是一致的,但它使汇编语言的子程序调用方法高级语言化。调用伪指令INVOKE的使用格式如下:INVOKE expression,arguments其中:expression 地址表达式,通常为子程序名;arguments 传递给子程序的参数列表,各参数之间用”,”分开,参数可以是寄存器、表达式或ADDR 标识符等。该伪指令是调用基于堆栈的子程序的方法,它把所有参数压栈,子程序结束时,又把参数自动弹出堆栈。在参数传递时,汇编程序将根据子程序的原型进行数据类型检查。若需要进行参数类型转换的话,汇编程序则自动生成一段代码来满足数据类型转换的要求。7.5.9 子程序的调用伪指令子程序的调
48、用伪指令第第7章章 子程序和库子程序和库例如:INVOKE TEST,AX,12+34,ADDR MSG其中:TEST是子程序名,寄存器AX和表达式“12+34”是参数,“ADDR MSG”是传递变量MSG的地址。例7.6 编写一个累加参数数值的子程序。其中参数的个数不定,参数的个数由第一个参数来确定。解:.MODEL SMALL.STACK 256.CODE;第一个参数parmcount确定其后面参数parmvalues中所含参数的个数ADDUP PROC NEAR C,parmcount:WORD,parmvalues:VARARGXORAX,AXXORSI,SIMOVCX,parmcou
49、nt.REPEAT ADDAX,parmvaluesSI INCSI INCSI.UNTILCXZRETADDUP ENDP.STARTUPINVOKE ADDUP,3,5,2,4;计算5+2+4INVOKE ADDUP,4,1,2,3,4;计算1+2+3+4.EXIT 0END第第7章章 子程序和库子程序和库 局部变量的定义格式:LOCAL 变量名数量:数据类型,变量名数量:数据类型.伪指令LOCAL的作用是说明一个或多个临时的局部变量(位于堆栈中)。局部变量必须在任何指令之前加以说明,可用多个LOCAL伪指令来说明局部变量。在子程序中,若说明了某个局部变量,则子程序体中的指令就可使用该局部
50、变量。汇编程序会把对它的引用转换成用指针寄存器BP来访问其在堆栈中的实际存储单元。局部变量只在当前子程序中使用,离开该子程序,它们就不能再被引用。但在局部变量的命名规则上有所不同,高级语言中的局部变量可与外层变量同名,而汇编语言中的局部变量不能与其它任何变量同名,否则,在汇编时,将会给出“重定义”(Symbol redefinition)的错误信息。7.5.10 局部变量的定义局部变量的定义第第7章章 子程序和库子程序和库 “数量”用来说明该变量所具有的元素个数,该数量必须写在括号“”之中。“数量”说明项是可选项。局部变量的类型说明符可以是任何合法的数据类型说明符。在16位段环境下,该缺省数据