汇编语言程序设计 ppt课件.ppt

汇编语言程序设计 ppt课件.ppt

ID:58873048

大小:760.00 KB

页数:275页

时间:2020-09-30

上传者:U-5649
汇编语言程序设计 ppt课件.ppt_第1页
汇编语言程序设计 ppt课件.ppt_第2页
汇编语言程序设计 ppt课件.ppt_第3页
汇编语言程序设计 ppt课件.ppt_第4页
汇编语言程序设计 ppt课件.ppt_第5页
资源描述:

《汇编语言程序设计 ppt课件.ppt》由会员上传分享,免费在线阅读,更多相关内容在教育资源-天天文库

第5章汇编语言程序设计5.1汇编语言的基本概念5.2汇编语言源程序的格式5.3伪指令语句5.4宏指令语句5.5汇编语言程序的上机过程5.6汇编语言程序设计的基本方法5.7发挥80386及其后继机型的优势 5.1汇编语言的基本概念1.机器语言(MachineLanguage)机器语言是一种用二进制表示指令和数据,能被机器直接识别的计算机语言。缺点:不直观,不易理解和记忆,因此编写、阅读和修改机器语言程序都比较繁琐。优点:机器语言程序是计算机惟一能够直接理解和执行的程序,具有执行速度快、占用内存少等特点。 任何计算机实际上只能直接识别设计微处理器时所规定好的,一整套用“0”、“1”数字代码表示的机器指令。这些机器指令的全体是指令系统。不同类型的CPU,其机器语言必然是不同的。这种直接用机器指令来编制计算机程序的方法就称为机器语言程序设计。《微机原理及应用》课程实验教学用的单板计算机就是直接采用机器语言编程的机器。这种直接用机器语言编程的方法难度大,阅读、查错和修改程序也很不方便。通常,只有当编程者对CPU指令系统比较熟悉,编写的程序较短时,才有可能直接用机器语言来编写计算机程序。但是,单板计算机对硬件操作很直观,可以帮助大家了解和理解计算机在实际中的应用。 2.高级语言(HighLevelLanguage)如果说机器语言是面向机器的语言,那么高级语言(如BASIC,FORTRAN等,C等)则是“面向过程”的语言。利用高级语言编程,程序员可以完全不考虑机器的结构特点,不必了解和熟记机器的指令系统,仅使用一些接近人们书写习惯的英语和数学表达式形式的语句去编制程序。利用高级语言编写的程序与问题本身的数学模型之间有着良好的对应关系,可在各种机器上通用(不同机器之间仅做少量修改)。但是,用高级语言编写的源程序并不能在机器上直接执行,需要被翻译成对应的目标程序(即机器语言程序),机器才能运行。把具有这种翻译作用的程序称为解释程序或编译程序,如图5.1所示。 图5.1编译程序的功能示意图由于高级语言程序是在未考虑机器的结构特点的条件下编写的,因而它就不能充分利用某种具体CPU所具有的某些特性,而通过编译或解释程序生成的目标程序往往比较冗长,占有较多的内存空间,执行时间也比较长,这就限制了它在某些场合下的运用。例如,实时的数据采集、检测和在线的实时控制等,往往要求程序的目标代码尽可能少占内存并有尽可能快的执行速度,在这些场合下,使用高级语言编写的程序常常不能满足要求。 3.汇编语言(AssemblyLanguage)人们为了摆脱机器语言编程中原始而低级的状态,就设法采用一组字母、数字或字符来代替机器指令,这样就产生了汇编语言的概念和方法。汇编语言是一种采用助记符表示的程序设计语言,即用助记符来表示指令的操作码和操作数,用标号或符号代表地址、常量或变量。助记符一般都是英文字的缩写,以方便人们书写、阅读和检查。实际上,用汇编语言编写的汇编语言源程序就是机器语言程序的符号表示,汇编语言源程序与其经过汇编所产生的目标代码程序之间有明显的一一对应关系,故也称汇编语言为符号语言。 用汇编语言编写程序能够直接利用硬件系统的特性(如寄存器、标志、中断系统等)直接对位、字节、字寄存器或存储单元、I/O端口进行处理,同时也能直接使用CPU指令系统和指令系统提供的各种寻址方式,编制出高质量的程序,这样的程序不但占用内存空间少,而且执行速度快。当然,由于源程序和所要解决的问题的数学模型之间的关系不够直观,使得汇编语言程序设计需要较多的软件开发时间,也增加了程序设计过程中出错的可能性。用汇编语言编写的源程序也需要翻译成目标程序才能被机器执行。这个翻译过程称为汇编,完成汇编任务的程序称为汇编程序,如图5.2所示。 图5.2汇编程序的功能示意图由于这种符号化的语言使用了用英文字母缩写表示的助记符,因此便于识别与记忆。 汇编程序是最早也是最成熟的一种系统软件。它除了能够将汇编语言源程序翻译成机器语言程序这一主要功能外,还能够根据用户的要求自动分配存储区域(包括程序区、数据区、暂存区等);自动地把各种进位制数转换成二进制数,把字符转换成ASCII码,计算表达式的值等;自动对源程序进行检查,给出错误信息(如非法格式,未定义的助记符、标号,漏掉操作数等)等。具有这些功能的汇编程序又称为基本汇编(或小汇编ASM)。在基本汇编的基础上,进一步允许在源程序中把一个指令序列定义为一条宏指令的汇编程序,就叫做宏汇编(MASM)。它包含全部ASM功能,还增加了宏指令、结构、记录等高级汇编语言功能。 问题:有高级语言,为什么还要学习和使用汇编语言?原因:(1)汇编语言非常接近机器语言程序,通过编制汇编语言程序,可以更清楚地了解计算机的工作过程。(2)现在的微机系统中,底层的一些功能仍然靠汇编语言程序来实现。(3)汇编语言程序的效率通常高于高级语言程序。 5.2汇编语言源程序的格式例5.1要求将两个5字节十六进制数相加。解:为实现上述功能,可以编写出以下汇编语言源程序。DATASEGMENT;定义数据段DATA1DB0F8H,60H,0ACH,74H,3BH;被加数DATA2DB0C1H,36H,9EH,0D5H,20H;加数DATAENDS;数据段结束CODESEGMENT;定义代码段ASSUMECS:CODE,DS:DATA START:MOVAX,DATAMOVDS,AX;初始化DSMOVCX,5;循环次数送CXMOVSI,0;置SI初值为0CLC;清CF标志LOOPER:MOVAL,DATA2[SI];取一个字节加数ADCDATA1[SI],AL;与被加数相加 INCSI;SI加1DECCX;CX减1JNZLOOPER;若不等于0,转LOOPERMOVAH,4CHINT21H;返回DOSCODEENDS;代码段结束ENDSTART;源程序结束 程序结构程序由数条语句构成,每条语句占一行。指令性语句(指令语句)指示性语句(伪指令语句)分段结构程序按段编写,与8086内存分段编址相对应。每段由伪操作SEGMENT开始、由ENDS结束。程序最后为END结束语句,后跟启动地址。启动地址指示程序开始执行的第一条语句。程序中设有返回DOS的功能。使程序执行完后返回DOS系统的命令接受状态。程序中用到内存操作数时,应按操作数的寻址方式,给相应的段寄存器赋值汇编语言程序结构:movs.asmaaSEGMENT;数据段1str1DB'Hello!’aaENDSbbSEGMENT;数据段2str2DB6dup(?)bbENDSccSEGMENT;代码段ASSUMECS:cc,DS:aa,ES:bbstart:CLDMOVAX,aaMOVDS,AXLEASI,str1MOVAX,SEGstr2MOVES,AXMOVDI,OFFSETstr2MOVCX,6REPMOVSBMOVAH,4CHINT21H;返回DOSccENDSENDstart;指示程序结束 5.2.1分段结构由例5.1可以看出,汇编语言源程序的结构是分段结构形式,一个汇编语言源程序由若干段(SEGMENT)组成,每个段以SEGMENT语句开始,以ENDS语句结束。整个源程序的结尾是END语句。这里所说的汇编语言源程序中的段与前面讨论的CPU管理的存储器的段,既有联系,又在概念上有所区别。我们已经知道,微处理器对存储器的管理是分段的,因此,在汇编语言程序中也要求分段组织指令、数据和堆栈,以便将源程序汇编成为目标程序后,可以分别装入存储器的相应段中。 但是,以8086/8088CPU为例,它有四个段寄存器(CS,ES,SS和DS),因此CPU对存储器按照四个物理段进行管理,即数据段、附加段、堆栈段和代码段。任何时侯CPU只能访问四个物理段。而在汇编语言源程序中,设置段的自由度比较大。例如,一个源程序中可以有多个数据段或多个代码段等等。一般来说,汇编语言源程序中段的数目可以根据实际需要而设定。为了和CPU管理的存储器物理段相区别,我们将汇编语言程序中的段称为逻辑段。在不致发生混淆的地方,有时简称为段。 在例5.1的简单源程序中只有两个逻辑段,一个逻辑段的名字是DATA,其中存放着与程序有关的数据,称为逻辑数据段;另一个逻辑段的名字是CODE,其中包含着程序的指令,称为逻辑代码段。每个段内均有若干行语句(STATEMENT),因此,可以说一个汇编源程序是由一行一行的语句组成的。下面我们来讨论汇编语言语句的类型和组成。 5.2.2汇编语言语句的类型和格式1.语句的类型汇编语言源程序中的语句可以分为两种类型:指令语句和伪指令语句。(1)指令语句:它是能产生目标代码,CPU可以执行的能完成特定功能的语句。(2)伪指令语句:它是一种不产生目标代码的语句,它仅仅在汇编过程中告诉汇编程序应如何汇编。例如,告诉汇编程序已写出的汇编语言源程序有几个段,段的名字是什么;定义变量,定义过程,给变量分配存储单元,给数字或表达式命名等。显然,伪指令语句是汇编程序在汇编时使用的。 2.语句的格式指令语句与伪指令语句的格式是类似的。一般情况下,汇编语言的语句可以由1~4部分构成:[名字]助记符[操作数][;注释]其中带方括号的部分表示任选项,可以有,也可以没有。例5.1中有如下语句:LOOPER:MOVAL,DATA2[SI];取一个字节加数这是一条指令语句,其中:“LOOPER:”是名字,“MOV”是指令助记符,“AL,DATA2[SI]”是操作数,“;”后面是注释部分。 例.DATA1DB0F8H,60H,0ACH,74H,3BH;定义被加数这是一条伪指令语句,其中:“DATA1”是名字,“DB”是伪指令定义符,“0F8H,60H,0ACH,74H,3BH”是操作数,“;”后面是注释部分。下面,对汇编语言中的各个组成部分进行讨论。 语句结构dataSEGMENT;数据段varDB?dataENDScodeSEGMENT;代码段ASSUMECS:code,DS:datastart:MOVAX,dataMOVDS,AXMOVvar,CLMOVAH,4CHINT21H;返回DOScodeENDSENDstart例: 1)名字汇编语言语句的第一个组成部分是名字(Name)。在指令语句中,这个名字是一个标号。指令语句中的标号实质上是指令的符号地址。并非每条指令语句必须有标号,但如果一条指令前面有一标号,则程序中其他地方就可以引用这个标号。在例5.1中,START、LOOPER就是标号。标号后面通常有一个冒号。标号有三种属性:段、偏移量和类型。 ①标号的段属性是定义标号在程序段的段地址。当程序中引用一个标号时,该标号的段值应在CS寄存器中。②标号的偏移量属性表示标号所在段的起始地址到定义该标号的地址之间的字节数。偏移量是一个16位无符号数。③标号的类型属性有两种:NEAR和FAR。NEAR标号可以在段内被引用,地址指针为2字节;FAR标号可以在其他段被引用,地址指针为4字节。如果定义一个标号时后面跟着冒号,则汇编程序确认其类型为NEAR。 伪指令语句中的名字可以是变量名、段名、过程名。与指令语句中的标号不同,这些伪指令语句中的名字并不总是任选的,有些伪指令规定前面必须有名字,有些则不允许有名字,也有一些伪指令的名字是任选的。即不同的伪指令对于是否有名字有不同的规定。伪指令语句的名字后面通常不跟冒号,这是它和标号的一个明显区别。很多情况下伪指令语句中的名字是变量名。变量名代表存储器中一个数据区的名字。例如,例5.1中的DATA1、DATA2就是变量名。 名字项用一个符号表示。对符号的规定:①由字符A-Z,a-z,0-9及符号@、$、下划线_等组成,最长31个字符,超出部分忽略。②不能用数字打头,以免与十六进制数相混。③不使用汇编程序中的保留字(如指令的助记符等)。④对定义的符号不区分大小写。 变量也有三种属性:段、偏移量和类型。①变量的段属性是变量所代表的数据区所在段的段地址。由于数据区一般在存储器的数据段中,因此变量的段地址常常在DS和ES寄存器中。②变量的偏移量属性是该变量所在段的起始地址与变量的地址之间的字节数。③变量的类型属性有BYTE(字节)、WORD(字)、DWORD(双字)、QWORD(四字)、TBYTE(十字)等,表示数据区中存取操作对象的大小。 2)助记符汇编语言语句中的第二个组成部分是助记符(Memonic)。在指令语句中的第二部分是CPU指令系统中指令的助记符,如MOV、ADC等。助记符约有90多种,在第4章指令系统中已经进行了讨论。在伪指令语句中的第二部分是伪指令的定义符,如DB、SEGMENT、ENDS、END等。它们在程序中的作用是定义变量的类型、定义段以及告诉汇编程序结束汇编等。关于伪指令的作用和使用方法,将在5.3节中讨论。 助记符可以是指令、伪操作中的助记符。①对于指令,汇编程序将其翻译成机器语言指令。MOVAX,100→B80001②对于伪操作,汇编程序根据其要求的功能进行处理。dataSEGMENT→data与一个段值相对应stringDB‘USTB’→string与一个内存地址相对应。 3)操作数汇编语言语句中的第三个组成部分是操作数。操作数项在指令语句中是指令的操作数,可能有单操作数或双操作数,也可能无操作数;操作数给出参与操作的数或数所在的地方。在伪指令中可能有更多个操作数。操作数多于一个时,用逗号分开。操作数可以是常数、寄存器、存储器操作数、标号名、过程名或表达式等。 (1)常数。常数就是指令中出现的那些固定值,可以分为数值常数和字符串常数两类。例如,立即数寻址时所有的立即数、直接寻址时所有的地址、ASCII字符串等都是常数。常数是除了自身的值以外,没有其他属性的数值。在源程序中,数值常数按其基数的不同,可有二进制数、八进制数、十进制数、十六进制数等几种不同表示形式。汇编语言用不同的后缀加以区别(如B,H等)。 常数:给出具体的数据。可以是数字常量或字符常量。◢◢数字默认十进制,也可加D表示十进制数。如1234D,1234◢◢数字后加B表示二进制数。如1010B◢◢数字后加H表示十六进制数。如1234H◢◢字符常量,用单引号表示。如‘1234’汇编时,用字符对应的ASCII表示。如31H,32H,33H,34H例data1DB12,34,56;十进制data2DB12H,34H,56H;十六进制MOVAL,‘G’;字符stringDB‘1234’;字符串 A、B、C、D、E、F开头的十六进制数前面加0,与H结尾的标识符区别。如寄存器名AH、BH、CH、DH变量名abcdH等例movAL,0AHmovAL,AHmovBX,0abcdH 汇编语言中的数值常数的第一位必须是数字,否则汇编时将被看成是标识符,如常数B7H应写成0B7H,FFH应写成0FFH。字符串常数是由单引号括起来的一串字符。例.‘ABCDEFG’和‘179’。单引号内的字符在汇编时都以ASCII的代码形式存放在存储单元中。如上述两字符串的ASCII代码为41H,42H,43H,44H,…,48H和31H,37H,39H。字符串最长允许有255个字符。 (2)寄存器。8086/8088CPU的寄存器可以作为指令的操作数。(3)标号。由于标号代表一条指令的符号地址,因此可以作为转移(无条件转移或条件转移)、过程调用CALL以及循环控制LOOP指令的操作数。(4)变量。因为变量是存储器中某个数据区的名字,所以在指令中可以作为存储器操作数。 (5)表达式。汇编语言语句中的表达式,按其性质可分为两种:数值表达式和地址表达式。数值表达式产生一个数值结果,只有大小,没有属性。地址表达式的结果不是一个单纯的数值,而是一个表示存储器地址的变量或标号,它有三种属性:段、偏移量和类型。 表达式中常用的运算符有以下几种:①算术运算符。常用的算术运算符有:+(加),−(减),*(乘),/(除)和MOD(模除,即两个整数相除后取余数)等。以上算术运算符可用于数值表达式,运算结果是一个数值。在地址表达式中通常只使用其中的+和-(加和减)两种运算符。 ②逻辑运算符。逻辑运算符有:AND(逻辑“与”),OR(逻辑“或”),XOR(逻辑“异或”)和NOT(逻辑“非”)。逻辑运算符只用于数值表达式中对数值进行按位逻辑运算,并得到一个数值结果。对地址进行逻辑运算是没有意义的。 ③关系运算符。关系运算符有:EQ(等于),NE(不等),LT(小于),GT(大于),LE(小于或等于),GE(大于或等于)等。参与关系运算的必须是两个数值或同一段中的两个存储单元地址,但运算结果只可能是两个特定的数值之一:当关系不成立(假)时,结果为0(全0);当关系成立(真)时,结果为0FFFFH(全1)。例如:MOVAX,4EQ3;关系不成立,故(AX)←0MOVAX,4NE3;关系成立,故(AX)←0FFFFH ④分析运算符分析运算符用于分析一个存储器操作数的属性,如段值、偏移量和类型等,或取得它所定义的存储空间的大小。分析运算符有SEG、OFFSET、TYPE、SIZE和LENGTH等。●SEG运算符。利用SEG运算符可以得到一个标号或变量所在段的段地址。例如,下面两条指令将变量ARRAY的段地址送DS寄存器。MOVAX,SEGARRAYMOVDS,AX ●OFFSET运算符利用OFFSET运算符可以得到一个标号或变量的偏移地址。例如:MOVDI,OFFSETDATA1指令执行时,将DATA1的偏移地址送到DI寄存器●TYPE运算符TYPE运算符的运算结果是一个数值,这个数值与存储器操作数类型属性的对应关系见表5.1。 下面是使用TYPE运算符的例子:VARDW?;变量VAR的类型为字ARRAYDD10DUP(?);变量ARRAY的类型为双字STRDB'THISISTEST';变量STR的类型为字节MOVAX,TYPEVAR;(AX)←2MOVBX,TYPEARRAY;(BX)←4MOVCX,TYPESTR;(CX)←1…… ●LENGTH运算符如果一个变量已用重复操作符DUP说明其变量的个数,则利用LENGTH运算符可得到这个变量的个数。如果未用DUP说明,则得到的结果总是1。例如,上面的例子中已经用“10DUP(?)”说明变量ARRAY的个数,则LENGTHARRAY的结果为10。 ●SIZE运算符如果一个变量已用重复操作符DUP说明,则利用SIZE运算符可得到分配给该变量的字节总数。如果未用DUP说明,则得到的结果是TYPE运算的结果。例如,上面的例子中变量ARRAY的个数为10,类型为DWORD(双字),因此,SIZEARRAY的结果为10×4=40。由此可知,SIZE的运算结果等于LENGTH的运算结果乘以TYPE的运算结果。 ⑤合成运算符。合成运算符可以用来建立或临时改变变量或标号的类型或存储器操作数的存储单元类型。合成运算符有PTR、THIS、SHORT等。●PTR运算符。PTR运算符可以指定或修改存储器操作数的类型,例如:INCBYTEPTR[BX][SI]指令中利用PTR运算符明确规定了存储器操作数的类型是BYTE(字节),因此,本指令将一个字节型存储器操作数加1。 利用PTR运算符可以建立一个新的存储器操作数,它与原来的同名操作数具有相同的段和偏移量,但可以有不同的类型。不过这个新类型只在当前语句中有效。例如:STUFFDD?;定义STUFF为双字类型变量MOVBX,WORDPTRSTUFF;从STUFF中取一个字到BX…… ●THIS运算符。THIS运算符也可指定存储器操作数的类型。使用THIS运算符可以使标号或变量更具灵活性。例如,要求对同一个数据区既可以字节为单位,又可以字为单位进行存取,则可用以下语句:TAB1EQUTHISWORDTAB2DB100DUP(?)上面TAB1和TAB2实际上代表同一个数据区,其中共有100个字节,但TAB1的类型为WORD(字类型),而TAB2的类型为BYTE(字节类型)。 ● SHORT运算符。SHORT运算符指定一个标号的类型为SHORT(短标号),即标号到引用该标号指令之间的距离在−128~+127个字节的范围内。短标号可以被用于无条件转移指令中。使用短标号的指令比使用缺省的近标号的指令少一个字节。 ⑥其他运算符。●段超越运算符“:”。运算符“:”(冒号)跟在段寄存器名(DS,ES,SS和CS)之后,表示段超越,用以给一个存储器操作数指定一个段属性,而不管其原来隐含的段是什么。例如:MOVAX,ES:[DI] ●字节分离运算符LOW和HIGH。运算符LOW和HIGH分别得到一个数值或地址表达式的低位和高位字节。例如:STUFFEQU0ABCDHMOVAH,HIGHSTUFF;(AH)←0ABHMOVAL,LOWSTUFF;(AL)←0CDH 以上介绍了表达式中使用的各种运算符,如果一个表达式同时具有多个运算符,则按以下规则运算:①优先级高的先运算,优先级低的后运算。②优先级相同时按表达式中从左到右的顺序运算。③括号可以提高运算的优先级,括号内的运算总是在相邻的运算之前进行。 表5.2运算符的优先级 4)注释汇编语言语句的最后一个组成部分是注释。对于一个汇编语言语句来说,注释部分并不是必要的,但是加上适当的注释以后,可以增加源程序的可读性。一个较长的实用程序,如果从头到尾没有任何注释,可能很难读懂。因此,最好在重要的程序段前面以及关键处加上简明扼要的注释。注释前面要求加上分号(;)。如果注释的内容较多,超过一行,则换行以后前面还要加上分号。注释也可以从一行的最前面开始,以表示对一个程序段的说明。汇编程序对于注释不予理会,即注释对汇编后产生的目标程序没有任何影响。 5.3伪指令语句伪指令无论表示形式或其在语句中所处的位置,都与指令相似。但二者之间有着重要的区别。首先,指令是给CPU的命令,在运行时由CPU执行,每条指令对应CPU的一种特定的操作,例如传送、加法等;而伪指令是给汇编程序的命令,在汇编过程中由汇编程序进行处理,例如定义数据、分配存储区、定义段以及定义过程等。其次,汇编以后,每条指令产生一一对应的目标代码;而伪指令则不产生与之相应的目标代码。宏汇编程序MASM提供了几十种伪指令,其中有一些伪指令小汇编ASM不能支持,如宏处理其等。根据其功能,伪指令大致可以分为以下几类: ●数据定义伪指令●符号定义伪指令●段定义伪指令●过程定义伪指令●宏处理伪指令●模块定义与连接伪指令●处理器选择伪指令●条件伪指令●列表伪指令●其他伪指令 5.3.1数据定义伪指令数据定义伪指令的用途是定义一个变量的类型,给变量赋初值,或者仅仅给变量分配存储单元,而不赋予特定的值。数据定义伪指令有DB,DW,DD,DF,DQ,DT等,而常用的是前三种。数据定义伪指令的一般格式为:[变量名]伪指令定义符操作数[,操作数…]其中方括号中的变量名为任选项,可以有,也可以没有。变量名后面不跟冒号。伪指令定义符后面的操作数可以不止一个。如有多个操作数,相互之间应该用逗号分开。 1.DB(DefineByte)定义变量的类型为字节(BYTE),给变量分配字节或字节串。DB伪指令定义符后面的操作数每个占有1个字节。2.DW(DefineWord)定义变量的类型为字(WORD)。DW伪指令定义符后面的操作数每个占有1个字,即2个字节。在内存中存放时,低位字在前,高位字在后。 3.DD(DefineDoubleword)定义变量的类型为双字(DWORD)。DD后面的操作数每个占有2个字,即4个字节。在内存中存放时,低位字在前,高位字在后。数据定义伪指令定义符后面的操作数可以是常数、表达式或字符串,但每项操作数的值不能超过由伪指令定义符所定义的数据类型限定的范围。例如,DB伪指令定义数据的类型为字节,则其范围为无符号数:0~255;带符号数:−128~+127,等等。字符串必须放在单引号中。另外,超过两个字符的字符串只能用DB伪指令定义。请看下列语句: DATADB101,0F0H;存入65H,F0HEXPRDB2*8+7;存入17HSTRDB'WELCOME!';存入8个字符的ASCII码值ABDB'AB';存入41H,42HBADW'AB';存入42H,41HABDDDD'AB';存入42H,41H,00,00OFFABDWAB;存入变量AB的偏移地址ADRSDWSTR,STR+3,STR+5;存入3个偏移地址TOTALDDDATA;先存DATA的偏移地址,再存段地址 以上第一和第二句中,分别将常数和表达式的值赋予一个变量。第三句的操作数是包含8个字符的字符串(只有DB伪指令才能用)。在第四、五、六句,注意伪指令DB、DW和DD的区别,虽然操作数均为'AB'两个字符,但存入变量的内容各不相同。第七句的操作数是变量AB,而不是字符串,此句将AB的16位偏移地址存入变量OFFAB。第八句存入三个等距的偏移地址,共占6字节。第九句中的DD伪指令定义符将DATA的偏移地址和段地址顺序存入变量TOTAL,共占2个字。 除了常数、表达式和字符串外,问号“?”也可以作为数据定义伪指令的操作数,此时仅给变量保留相应的存储单元,而不赋予变量某个确定的初值。当同样的操作数重复多次时,可用重复操作符“DUP”表示,其形式为:nDUP(初值[,初值,…])其中圆括号中为重复的内容,n为重复次数。如果用“nDUP(?)”作为数据定义伪指令定义符的惟一操作数,则汇编程序产生一个相应的数据区,但不赋任何初值。重复操作符“DUP”可以嵌套。下面是用问号或“DUP”表示操作数的几个例子: FILLERDB?SUMDW?DB?,?,?BUFFERDB10DUP(?)ZERODW30DUP(0)MASKDB5DUP('OK!')ARRAYDB100DUP(3DUP(8),6) 其中第一、第二句分别给字节变量FILLER和字变量SUM分配存储单元,但不赋予特定的值。第三句给一个没有名称的字节变量赋予3个不确定的值。第四句给变量BUFFER分配10个字节的存储空间,但不赋任何初值。第五句给变量ZERO分配一个数据区,共30个字(即60字节),每个字的内容均为零。第六句定义一个数据区,其中有5个重复的字符串'OK!',共占15字节。最后一句将变量ARRAY定义为一个数据区,其中包含重复100次的内容:8,8,8,6,共占400个字节。 通常把用DUP作为惟一操作数而定义的变量称为数组。下面是几个错误的数据定义伪指令语句:ERROR1:DW99;变量名后有冒号ERROR2DB25*90;DB的操作数超过255ERROR3DD‘1234’;DD的操作数是超过2个字符的字符串 5.3.2符号定义伪指令符号定义伪指令的用途是给一个符号重新命名,或定义新的类型属性等。符号包括汇编语言的变量名、标号名、过程名、寄存器名以及指令助记符等。常用的符号定义伪指令有EQU、=(等号)和LABLE。 1.EQU格式:名字EQU表达式EQU伪指令将表达式的值赋予一个名字。以后可用这个名字来代替上述表达式。格式中的表达式可以是一个常数、符号、数值表达式或地址表达式等。例如:CREQU0DH;常数LFEQU0AHAEQUASCII_TABLE;变量 STREQU64*1024;数值表达式ADREQUES:[BP+DI+5];地址表达式CBDEQUAAM;指令助记符利用EQU伪指令,可以用一个名字代表一个数值,或用一个较简短的名字来代替一个较长的名字。如果源程序中需要多次引用某一表达式,则可以利用EQU伪指令定义符给其赋一个名字,以代替程序中的表达式,从而使程序更加简洁,便于阅读。将来如果改变表达式的值,也只需修改一处,使程序易于维护。需要注意一个问题:EQU伪指令不允许对同一符号重复定义。 2.=(等号)格式:名字=表达式=(等号)伪指令的功能与EQU伪指令基本相同,主要区别在于它可以对同一个名字重复定义。例如:COUNT=100MOVCX,COUNT;(CX)←100COUNT = COUNT−10MOVBX,COUNT;(BX)←90…… 3.LABLE格式:名字LABLE类型LABLE伪指令的用途是定义标号或变量的类型。变量的类型可以是BYTE、WORD、DWORD等;标号的类型可以是NEAR或FAR。利用LABEL伪指令可以使同一个数据区兼有BYTE和WORD两种属性,这样,在以后的程序中可根据不同的需要分别以字节或字为单位存取其中的数据。例如: AREAWLABELWORD;变量AREAW的类型为WORDAREABDB100DUP(?);变量AREAB的类型为BYTEMOVAREAW,AX;AX送第1和第2字节中MOVAREAB[49],AL;AL送第50字节中…… LABEL伪指令也可以将一个属性已经定义为NEAR或者后面跟有冒号(隐含属性为NEAR) 的标号再定义为FAR。例如:AGAINFLABELFAR;定义标号AGAINF的属性为FARAGAIN:PUSHAX;定义标号AGAIN的属性为NEAR上面的过程既可以利用标号AGAIN在本段内被调用,也可以利用标号AGAINF被其他段调用。 5.3.3段定义伪指令段定义伪指令的用途是在汇编语言源程序中定义逻辑段。常用的段定义伪指令有SEGMENT/ENDS和ASSUME等。1.SEGMENT/ENDS格式:段名SEGMENT[定位类型][组合类型]['类别']段名ENDS… SEGMENT伪指令用于定义一个逻辑段,给逻辑段赋予一个段名,并以后面的任选项(定位类型、组合类型、‘类别’ )规定该逻辑段的其他特性。SEGMENT伪指令位于一个逻辑段的开始部分,而ENDS伪指令则表示一个逻辑段的结束。在汇编语言源程序中,这两个伪指令定义符总是成对出现的,二者前面的段名必须一致。两个语句之间的部分即是该逻辑段的内容。例:对于代码段,其中主要有指令及其他伪指令;对于数据段和附加段,主要有定义数据区的伪指令等等。一个源程序中不同逻辑段的段名可以各不相同。 SEGMENT伪指令后面还有三个任选项:定位类型、组合类型和‘类别’。在上面的格式中,它们都放在方括号内,表示可有可无。如果有,三者的顺序必须符合格式中的规定。SEGMENT伪指令后面的这些任选项是给汇编程序(MASM)和连接程序(LINK)的命令。任选项告诉汇编程序和连接程序,如何确定段的边界,以及如何组合几个不同的段等。下面分别进行讨论。 1)定位(Align)类型定位类型任选项告诉汇编程序如何确定逻辑段的边界在存储器中的位置。定位类型共有以下四种:●BYTE(边界起始地址=××××××××××××××××B)该类型表示逻辑段从一个字节的边界开始,即可以从任何地址开始。此时本段的起始地址可紧接在前一个段的后面。 ●WORD(边界起始地址=×××××××××××××××0B)该类型表示逻辑段从字的边界开始。2字节为1个字,此时本段的起始地址必须是偶数。例:边界起始地址的十六进制表示可以是×××0H或×××2H或×××4H或×××8H●PARA(边界起始地址=××××××××××××0000B)该类型表示逻辑段从一个节(Paragraph)的边界开始(一节等于16个字节),也即段的起始地址能被16整除,故本段的起始地址(十六进制)应为×××0H。如果省略定位类型任选项,则默认其为PARA。 ●PAGE(边界起始地址=××××××××00000000B)该类型表示逻辑段从页边界开始(一页等于256个字节),也即段的起始地址能被256整除,故本段的起始地址(十六进制)应为××00H。●$(地址计数器伪指令)在汇编语言程序内,为了指示下一个数据或指令在相应段中的偏移量,汇编程序使用了一个当前位置计数器$。例.STR1DW’AB’STR2DB16DUP(?)CNTEQU$-STR1MOVCX,CNT指令执行后,CNT=$-STR1=2+16=18=12H,所以(CX)=12H 例5.2SEGMENT伪指令定义符的定位类型应用举例。STACKSEGMENTSTACK;STACK段,定位类型缺省DB100DUP(?);长度为100字节STACKENDS;STACK段结束DATA1SEGMENTBYTE;DATA1段,定位类型BYTESTRINGDB'Thisisanexample!';长度为19字节DATA1ENDS;DTAT1段结束DATA2SEGMENTWORD;DATA2段,定位类型WORDBUFFERDW40DUP(0);长度为40个字(80字节)DATA2ENDS;DATA2段结束 CODE1SEGMENTPAGE;CODE1段,定位类型PAGE;假设CODE2段长度为13字节CODE1ENDS;CODE1段结束CODE2SEGMENT;CODE2段,定位类型缺省START:MOVAX,STACK;建立堆栈段MOVSS,AX;假设CODE2段长度为52字节CODE2ENDS;CODE2段结束ENDSTART;源程序结束 本例的源程序中共有五个逻辑段,它们的段名、定位类型和已知条件分别如下:STACK段PARASTACK段的长度为100字节(64H)DATA1段BYTEDATA1段的长度为19字节(13H)DATA2段WORDDATA2段的长度为40个字CODE1段PAGECODE1段占用13字节(0DH)CODE2段PARACODE2段占用52字节(34H)已经知道其中STACK段的长度为100字节(64H),DATA1段的长度为19字节(13H),DATA2段的长度为40个字,即80字节(50H)。假设CODE1段占用13字节(0DH),CODE2段占用52字节(34H)。如果将以上逻辑段进行汇编和连接,然后再来观察各逻辑段的目标代码或数据装入存储器的情况,如表5.3。 表5.3例5.2各逻辑段的起始地址和结束地址段名定位类型字节数起始地址结束地址STACKPARA100(64H)00000H00063HDATA1BYTE19(13H)00064H00076HDATA2WORD80(50H)00078H000C7HCODE1PAGE13(0DH)00100H0010CHCODE2PARA52(34H)00110H00143H由表可清楚地看出,当SEGMENT伪指令的定位类型不同时,对段起始边界的规定也不相同。 aaSEGMENT;数据段1str1DB'Hello!’aaENDSbbSEGMENT;数据段2str2DB6dup(?)bbENDSccSEGMENT;代码段ASSUMECS:ccASSUMEDS:aa,ES:bbstart:CLDMOVAX,aaMOVDS,AXLEASI,str1MOVAX,bbMOVES,AXLEADI,str2MOVCX,6REPMOVSBMOVAH,4CHINT21HccENDSENDstartD:masm>DEBUGhello2.exe-U:查看代码段12A0:0000FCCLD12A0:0001B89E12MOVAX,129E12A0:00048ED8MOVDS,AX12A0:00068D360000LEASI,[0000]12A0:000AB89F12MOVAX,129F12A0:000D8EC0MOVES,AX12A0:000F8D3E0000LEADI,[0000]12A0:0013B90600MOVCX,000612A0:0016F3REPZ12A0:0017A4MOVSB12A0:0018B44CMOVAH,4C12A0:001ACD21INT21、、、-D129E:0L10;查看数据段1的内容129E:000048656C6C6F210000-0000000000000000Hello!..........-D129F:0L10;查看数据段2的内容129F:00000000000000000000-0000000000000000................-数据传送源程序:   程序经汇编、连接后,装入内存的情况如下: 2)组合(Combine)类型SEGMENT伪指令的第二个任选项是组合类型,它告诉汇编程序当装入存储器时各个逻辑段如何进行组合。组合类型共有以下六种。(1)不组合如果SEGMENT伪指令的组合类型任选项缺省,则汇编程序认为这个逻辑段是不组合的。也就是说,不同程序中的逻辑段,即使具有相同的段名,也分别作为不同的逻辑段装入内存,不进行组合。但是,对于组合类型任选项缺省的同名逻辑段,如果属于同一个程序模块,则被集中成为一个逻辑段。 (2)PUBLIC连接时,对于不同程序模块中的逻辑段,只要具有相同的段名,就把这些段集中成为一个逻辑段装入内存。(3)STACK组合类型为STACK时,其含意与PUBLIC基本一样,即不同程序中的逻辑段,如果段名相同,则集中成为一个逻辑段。不过组合类型STACK仅限于作为堆栈区域的逻辑段使用。注意:在执行程序(.EXE)中,堆栈指针SP设置在这个集中以后的堆栈段的(最终地址+1)处。 (4)COMMON连接时,对于不同程序中的逻辑段,如果具有相同的段名,则都从同一个地址开始装入,因而各个逻辑段将发生重叠。最后,连接以后段的长度等于原来最长的逻辑段的长度,重叠部分的内容是最后一个逻辑段的内容。(5)MEMORY该类型表示当几个逻辑段连接时,本逻辑段定位在地址最高的地方。如果被连接的逻辑段中有多个段的组合类型都是MEMORY,则汇编程序只将首先遇到的段作为MEMORY段,而其余的段均当作COMMON段处理。(6)AT表达式这种组合类型表示本逻辑段根据表达式的值定位段地址。例如,AT8A00H,表示本段的段地址为8A00H,则本段从存储器的物理地址8A000H开始装入。 3)'类别'('Class')SEGMENT伪指令的第三个任选项是 ‘类别’ ,类别必须放在单引号内。‘类别’ 的作用是在连接时决定各逻辑段的装入顺序。当几个程序模块进行连接时,其中具有相同类别名的逻辑段被装入连续的内存区,类别名相同的逻辑段,按出现的先后顺序排列。没有类别名的逻辑段,与其他无类别名的逻辑段一起连续装入内存。例如,设一个主程序中有五个逻辑段,段名和类别名分别为:STK1段'STACK'CODE1段无DATA1段'BUFFER'DATA2段'TABLE'DATA3段'BUFFER' 还有一个子程序,包括四个逻辑段,段名和类别名分别为:DATA4段'TABLE'DATA5段'BUFFER'STK2段'STACK'CODE2段无当将上述主程序和子程序进行连接时,两个程序模块中各逻辑段装入内存的顺序见图5.3。 图5.3逻辑段按类别装入内存的示意图 2.ASSUME格式:ASSUME段寄存器名:段名[,段寄存器名:段名,…]ASSUME伪指令告诉汇编程序,将某一个段寄存器设置为存放某一个逻辑段的段地址,即明确指出源程序中的逻辑段与物理段之间的关系。当汇编程序汇编一个逻辑段时,即可利用相应的段寄存器寻址该逻辑段中的指令或数据。在一个源程序中,ASSUME伪指令定义符应该放在可执行程序开始位置的前面。注意:ASSUME伪指令只是通知汇编程序有关段寄存器与逻辑段的关系,并没有给段寄存器赋予实际的初值。例如: CODESEGMENTASSUMECS:CODE,DS:DATA1,SS:STACKMOVAX,DATA1MOVDS,AX;给DS赋值MOVAX,STACKMOVSS,AX;给SS赋值CODEENDS… ASSUME伪操作的作用:指示汇编程序指令中用到的标号、过程及变量所在的段。其中:对标号、过程必须用CS段寄存器指示对变量可用CS、DS、ES、SS段寄存器指示若未用ASSUME语句指示指令中用到的标号、过程和变量所在的段,汇编程序将给出错误信息。ASSUME语句只起指示作用,并无实际的操作。在程序中引用定义的变量做内存操作数时,需按寻址方式用传送指令(如MOV)给相应的段寄存器赋值。 例用程序实现1234H+5678HdataSEGMENTvalueDW1234H,5678HresultDW?dataENDScodeSEGMENTASSUMECS:code,DS:datastart:MOVAX,data;给DS赋值MOVDS,AXMOVAX,value;取数ADDAX,value+2;两数相加MOVresult,AX;保存结果MOVAH,4CH;返回DOSINT21HcodeENDSENDstart 注释掉ASSUME语句,其汇编结果:dataSEGMENTvalueDW1234H,5678HresultDW?dataENDScodeSEGMENT;ASSUMECS:code,DS:datastart:MOVAX,dataMOVDS,AXMOVAX,valueADDAX,value+2MOVresult,AXMOVAH,4CHINT21HcodeENDSENDstartD:MASM>MASMassume;Microsoft(R)MacroAssemblerVersion5.10Copyright(C)MicrosoftCorp1981,1988.Allrightsreserved.assume.ASM(8):MissingorunreachableCSassume.ASM(10):Cannotaddresswithsegmentregisterassume.ASM(11):Cannotaddresswithsegmentregisterassume.ASM(12):Cannotaddresswithsegmentregister49872+421341Bytessymbolspacefree0WarningErrors4SevereErrorsD:MASM> 程序中有ASSUME语句,经汇编,连接和装入内存后的情况:dataSEGMENTvalueDW1234H,5678HresultDW?dataENDScodeSEGMENTASSUMECS:code,DS:datastart:MOVAX,data;给DS赋值MOVDS,AXMOVAX,value;取数ADDAX,value+2;两数相加MOVresult,AX;保存结果MOVAH,4CH;返回DOSINT21HcodeENDSD:MASM>DEBUGassume.exe-R;查看程序执行前各寄存器AX=0000BX=0000CX=0023DX=0000SP=0000BP=0000SI=0000DI=0000DS=1295ES=1295SS=12A5CS=12A6IP=0000NVUPEIPLNZNAPONC12A6:0000B8A512MOVAX,12A5-U;查看在内存的程序12A6:0000B8A512MOVAX,12A512A6:00038ED8MOVDS,AX12A6:0005A10000MOVAX,[0000]12A6:000803060200ADDAX,[0002]12A6:000CA30400MOV[0004],AX12A6:000FB44CMOVAH,4C12A6:0011CD21INT21…… 注意:程序装入内存后,执行程序前,当前的DS值并非程序定义的data段值,1295不等于12A5。结论:1.ASSUME语句只起指示作用,没有赋值作用;2.程序中用到内存操作数时,应按操作数的寻址方式,给相应的段寄存器赋值。 5.3.4过程定义伪指令过程也就是子程序,所以过程定义伪指令也就是子程序定义伪指令。格式:过程名PROC[NEAR/FAR]RET过程名ENDP…… 其中,PROC伪指令定义一个过程(子程序),赋予过程一个名字,并指出该过程的属性为NEAR或FAR。如果没有特别指明类型,则认为过程的类型是NEAR。伪指令ENDP标志过程的结束。上述两个伪指令前面的过程名必须一致,且成对出现。当一个程序段被定义为过程后,程序中其他地方就可以用CALL指令调用这个过程。调用一个过程的格式为:CALL过程名 过程名实质上是过程入口的符号地址,它和标号一样,也有三种属性:段、偏移量和类型。过程的类型属性可以是NEAR或FAR。一般来说,被定义为过程的程序段中应该有返回指令RET,但不一定是最后一条指令,也可以有不止一条RET指令。执行RET指令后,控制返回到原来调用指令的下一条指令。过程的定义和调用均可嵌套。例如: NAME1PROCFARCALLNAME2RETNAME2PROCNEARRETNANE2ENDPNAME1ENDP……… 5.3.5模块定义与连接伪指令在编写规模比较大的汇编语言程序时,可以将整个程序划分成为几个独立的源程序(或称模块),然后将各个模块分别进行汇编,生成各自的目标程序,最后将它们连接成为一个完整的可执行程序。各个模块之间可以相互进行符号访问。也就是说,在一个模块中定义的符号可以被另一个模块引用。通常称这类符号为外部符号,而将那些在一个模块中定义,只在同一模块中引用的符号称为局部符号。为了进行连接以及在这些将要连接在一起的模块之间实现互相的符号访问,以便进行变量传送,常常使用以下几个伪指令:NAME、END、PUBLIC和EXTRN。 1.NAMENAME伪指令用于给源程序汇编以后得到的目标程序指定一个模块名,连接时需要使用这个目标程序的模块名。其格式为:NAME模块名NAME的前面不允许再加上标号,例如下面的表示方式是非法的:BEGIN:NAMEMODNAME(错误)如果程序中没有NAME伪指令,则汇编程序将TITLE伪指令(TITLE属于列表伪指令)后面“标题名”中的前六个字符作为模块名。如果源程序中既没有使用NAME,也没有使用TITLE伪指令,则汇编程序将源程序的文件名作为目标程序的模块名。 2.ENDEND伪指令表示源程序到此结束,指示汇编程序停止汇编,对于END后面的语句不予理会。其格式为:END[标号]END伪指令后面的标号表示程序执行的开始地址。END伪指令将标号的段地址和偏移地址分别提供给CS和IP寄存器。方括号中的标号是任选项。如果有多个模块连接在一起,则只有主模块的END语句使用标号。 3.PUBLICPUBLIC伪指令说明本模块中的某些符号是公共的,即这些符号可以提供给将被连接在一起的其他模块使用。其格式为:PUBLIC符号[,…]其中的符号可以是本模块中定义的变量、标号或数值的名字,包括用PROC伪指令定义的过程名等。PUBLIC伪指令可以安排在源程序的任何地方。 4.EXTRNEXTRN伪指令说明本模块中所用的某些符号是外部的,即这些符号在将被连接在一起的其他模块中定义(在定义这些符号的模块中还必须用PUBLIC伪指令说明)。其格式为:EXTRN名字:类型[,…]其中的名字必须是其他模块中定义的符号;类型必须与定义这些符号的模块中的类型说明一致。如为变量,类型可以是BYTE、WORD或DWORD等;如为标号和过程,类型可以是NEAR或FAR;如果是初值,类型可以是ABS,等等。 5.3.6处理器选择伪指令由于80x86的所有处理器都支持8086/8088指令系统,但每一种高档的机型又都增加一些新的指令,因此,在编写程序时要对所用处理器有一个确定的选择,要告诉汇编程序应该选择哪一种指令系统。处理器选择伪指令的功能就是指明这种任务。此类伪指令主要有以下几种: .8086选择8086指令系统.286选择80286指令系统.286P选择保护方式下的80286指令系统.386选择80386指令系统.386P选择保护方式下的80386指令系统.486选择80486指令系统.486P选择保护方式下的80486指令系统.586选择Pentium指令系统.586P选择保护方式下的Pentium指令系统 有关“选择保护方式下的××××指令系统”的含义是指包括特权指令在内的指令系统。此外,上述伪指令均支持相应的协处理器指令。这类伪指令一般放在整个程序的最前面。如不给出,则汇编程序认为其默认值为 .8086。它们也可放在程序中,如程序中使用了一条80486所增加的指令,则可在该指令的上一行加上.486。 5.4宏指令语句5.4.1常用的宏处理伪指令1.宏定义伪指令(MACRO/ENDM)格式:宏指令名MACRO(宏定义体)ENDM… MACRO是宏定义符,它将一个宏指令名定义为宏定义体中包含的程序段。ENDM表示宏定义结束,前面不需要有宏指令名。进行一次宏定义,以后就可以多次用宏指令名进行宏调用。但是必须先定义,后调用。当汇编时,MASM对每个宏指令名自动用相应宏定义体中的程序段代替,这个过程称为宏扩展。总之,使用宏指令的过程共有三步:首先进行宏定义;然后进行宏调用;最后,汇编时由MASM进行宏扩展。宏定义允许嵌套,即宏定义体中可以包含另一个宏定义,而且宏定义体中也可以有宏调用,但是也必须先定义,后调用。 例5.3若源程序中多处需要将BL和CL寄存器中两个压缩的BCD数相加,并将和送回BL寄存器,则可如下定义宏指令,然后在需要的地方进行调用。BCDADDMACROMOVAL,BLADDAL,CLDAAMOVBL,ALENDM 宏定义允许带参数,此时可定义的宏指令具有较强的通用性。带参数的宏定义格式如下所示:宏指令名MACRO参数[,参数,…](宏定义体)ENDM…以上宏定义中的参数称为形式参数(dummyparameter),或称哑元。当形式参数不止一个时,相互之间要用逗号分开。以后宏调用时,应在宏指令名后面写上相应的实际参数(ActualParameter),或称实元。 一般情况下,实际参数与形式参数的个数和顺序均为一一对应的。但是,汇编程序允许二者的个数不等。当实际参数多于形式参数时,多余的实际参数将被忽略;当形式参数多于实际参数时,认为多余的形式参数为空。例5.4DECADD1MACROOPR1,OPR2MOVAL,OPR1ADDAL,OPR2DAAMOVOPR1,ALENDM 利用本例中的宏定义可对分别存放在任何8位寄存器或存储单元中的两个压缩BCD数进行加法运算。例如有以下宏调用:DECADD1DL,BUFFERDECADD1AREA1,AREA2汇编时进行宏扩展,得到以下指令:DECADD1DL,BUFFER扩展为:+MOVAL,DL+ADDAL,BUFFER+DAA+MOVDL,AL DECADD1AREA1,AREA2扩展为:+MOVAL,AREA1+ADDAL,AREA2+DAA+MOVAREA1,AL宏扩展后,原来宏定义体中的指令前面加上了符号“+”,以示区别。 2.声明宏体内局部标号的伪指令(LOCAL)LOCAL的作用是声明宏体中的局部标号,以免在宏扩展时,同一个标号在源程序中多次出现,从而产生标号多重定义的错误。LOCAL伪指令必须位于宏体内其他所有语句(包括注释)之前,其格式为:LOCAL局部标号[,…]例如,下面的宏指令完成将寄存器中的一位十六进制数转换为相应的ASCII码,由于宏体中出现局部标号,因此必须使用LOCAL伪指令对宏体中的局部标号进行声明。 HEXTOASCMACROREGLOCALNUMCMPREG,0AHJCNUMADDREG,07HNUM:ADDREG,30HENDM 5.4.2宏指令与子程序的区别宏指令是用一条指令来代替一段程序,以简化源程序的设计,子程序(过程)也有类似的功能。宏指令与子程序的区别主要表现在以下几方面:(1)宏指令由宏汇编程序MASM在汇编过程中进行处理,在每个宏调用处,将相应的宏体插入;而子程序调用指令CALL和返回指令RET则是CPU指令,执行CALL指令时,CPU使程序控制转移到子程序的入口地址。 (2)宏指令简化了源程序,但不能简化目标程序。汇编以后,在宏定义处不产生机器代码,但在每个宏调用处,通过宏扩展,宏体中指令的机器代码被插入到宏调用处,因此不节省内存单元;子程序在目标程序中定义子程序的地方将产生相应的机器代码,但每次调用时,只需用CALL指令,不再重复出现子程序的机器代码,一般来说可以节省内存单元。(3)从执行时间来看,调用子程序和从子程序返回需要保护断点、恢复断点等,这些都将额外占用CPU的时间,而宏指令则不需要,因此相对子程序来说,宏指令的执行速度较快。此外,宏指令更加接近高级语言,而且传送参数更加方便。 5.5汇编语言程序的上机过程当我们上机操作,在计算机上建立和运行汇编语言程序时:首先要用编辑程序(如全屏幕编辑程序EDIT或行编辑程序EDLIN等)建立汇编语言源程序(其扩展名必须为.ASM)。源程序就是用汇编语言的语句编写的程序。汇编语言源程序不能被计算机所识别和运行,必须经过汇编程序(MASM或ASM)加以汇编(翻译),把源程序文件转换成为用机器码(二进制代码)表示的目标程序文件(其扩展名为.OBJ)。若在汇编过程中没有出现语法错误,则汇编结束后,还必须经过连接程序(LINK)把目标程序文件与库文件或其他目标文件连接在一起形成可执行文件(其扩展名为.EXE文件)。这时,就可以在DOS下直接键入文件名运行此程序了。 上机环境硬件系统软件应用软件操作系统:DOS系统编辑器:EDIT.exe编程序:MASM.exe连接程序:LINK.exe调试程序:DEBUG.exe用户开发的程序:ABC.exe等CPU、存储器(ROM、RAM)、I/O接口、输入、输出设备 因此,在计算机上运行汇编语言程序的步骤是:(1)用编辑程序(EDIT)建立ASM源程序文件。(2)用汇编程序(MASM或ASM)把ASM文件汇编成OBJ文件。(3)用连接程序(LINK)把OBJ文件转换成EXE文件。(4)在DOS命令状态下直接键入文件名就可执行该文件。 图5.4汇编语言程序上机过程 5.5.1用编辑程序建立汇编语言源程序文件(ASM文件)例:建立一个多字节相加的汇编语言源程序。解:可以在DOS模式下,用编辑程序EDIT.EXE建立汇编语言源程序文件MBA.ASM(多字节相加程序)。操作方法如下:C>EDITMBA.ASM即可进入EDIT的程序编辑画面。输入汇编语言源程序如下:DATASEGMENT;定义数据段ARRY1DB10,21,32,53,64,75,96,10,11ARRY2DB21,17,35,15,50,26,41,42,28ARRY3DB9DUP(?)NDW9DATAENDS;数据段结束 STACKSEGMENTPARASTACK'STACK';定义堆栈段DW100DUP(?)STACKENDS;堆栈段结束CODESEGMENT;定义代码段ASSUMECS:CODE,DS:DATA,SS:STACKMAINPROCFAR;主程序部分START:PUSHDS;将DS压入堆栈保存MOVAX,0PUSHAX;将0压入堆栈保存MOVAX,DATA;把数据段地址送AX MOVDS,AX;然后通过AX送入DSMOVAX,STACK;把堆栈段地址送AXMOVSS,AX;然后通过AX送入SSMOVSI,OFFSETARRY1;把ARRY1的偏移量地址送入SIMOVDI,OFFSETARRY2;把ARRY2的偏移量地址送入DIMOVCX,NCLC;进位标志清零CALLADDFA;调用子程序ADDFARET;返回DOS MAINENDP;主程序结束ADDFAPROCNEAR;定义子程序PUSHAX;保护现场PUSHCXPUSHSIPUSHDIMOVBX,OFFSETARRY3;把ARRY3的偏移量地址送入BXLOOP1:MOVAL,[SI];取一个字节加数ADDAL,[DI];与另一个字节加数相加MOV[BX],AL;结果送存INCSI;ARRY1的偏移量地址加1 INCDI;ARRY2的偏移量地址加1INCBX;ARRY3的偏移量地址加1LOOPLOOP1;未加完转LOOP1POPDI;恢复现场POPSIPOPCXPOPAXRET;返回主程序ADDFAENDP;子程序结束CODEENDS;代码段结束ENDSTART;汇编结束 5.5.2用汇编程序MASM将ASM文件汇编成目标程序文件(OBJ文件)在对源程序文件(简称ASM文件)汇编时,汇编程序将对ASM文件进行两遍扫描,若程序文件中有语法错误,则结束汇编后,汇编程序将指出源程序中存在的错误,这时应返回编辑环境修改源程序中的错误,再经过汇编,直到最后得到无错误的目标程序,即OBJ文件。因此,汇编程序的主要功能可以概括为以下三点:(1)检查源程序中的语法错误,并给出错误信息。(2)产生目标程序文件(OBJ文件)。(3)展开宏指令。 完成汇编功能的是宏汇编程序MASM或汇编程序ASM,二者的区别在于:MASM有宏处理功能,而ASM没有宏处理功能;MASM比ASM的功能强大;MASM需要占据较大的内存空间。当内存空间较小时(如64KB),则只能使用ASM。例:仍以MBA.ASM程序为例,说明汇编过程。当源程序建立以后,用汇编程序MASM对MBA.ASM源程序文件进行汇编,以便产生机器码的目标程序文件MBA.OBJ。具体操作步骤如下: C>MASMMBAMicrosoft(R)MacroAssemblerVersion5.00Copyright(C)MicrosoftCorp1981–1985,1987.Allrightsreserved.Objectfilename[MBA.OBJ]:Sourcelisting[NUL.LST]:MBACross-reference[NUL.CRF]:MBA50468+303948Bytessymbolspacefree0WarningErrors0SevereErrors 汇编程序调入后,首先显示版本号,然后出现三个提示行。第一个提示行:Objectfilename[MBA.OBJ]:这是询问目标程序文件名,方括号内为机器规定的默认的文件名,通常直接按回车键,表示采用默认的文件名(如上所示)。第二个提示行:Sourcelisting[NUL.LST]:这是询问是否建立列表文件。若不建立,直接回车;若要建立,则输入文件名再回车(如上所示,表示要建立名为MBA的列表文件)。列表文件中同时列出源程序和机器语言程序清单,并给出符号表,有利于程序调试。第三个提示行:Cross-reference[NUL.CRF]:这是询问是否要建立交叉索引文件。若不建立,则直接回车;若要建立,则应输入文件名(如上所示,表示要建立MBA.CRF文件)。 为了建立交叉索引文件,还必须调用CREF.EXE程序,即输入:C>CREFMBAMicrosoft(R)Cross-ReferenceUtilityVersion5.00Copyright(C)MicrosoftCorp1981–1985,1987.Allrightsreserved.listing[MBA.REF]:11Symbols这时首先显示版本号,然后出现一个提示行:Listing[MBA.REF]:这是询问交叉索引文件名。这时可用回车承认方括号内机器默认的文件名,如上所示。这样就建立了MBA.REF文件。其内容是用户定义的所有符号(包括变量),并给出每个符号定义所在的行号(附以#)以及引用的行号。 调入汇编程序,当我们回答了上述各提示行的询问之后,汇编程序就对源程序进行汇编。若汇编过程中发现源程序有语法错误,则列出有错误的语句和错误的代码。错误分警告错误(WraningErrors)和严重错误(SevereErrors)。警告错误是指汇编程序认为的一般性错误;严重错误是指汇编程序认为无法进行正确汇编的错误,并给出错误的个数及行号、错误的性质等。这时,就要对错误进行分析,找出问题和原因,然后再调用编辑程序加以修改,修改后重新汇编,直到汇编后无错误为止。 5.5.3用连接程序LINK生成可执行程序文件(EXE文件)经汇编后产生的二进制的目标程序文件(OBJ文件)并不是可执行程序文件(EXE文件),必须经连接以后,才能成为可执行文件。连接程序并不是专为汇编语言程序设计的。如果一个程序是由若干个模块组成的,也可通过连接程序LINK把它们连接在一起。这些模块可以是汇编程序产生的目标文件,也可以是高级语言编译程序产生的目标文件。 连接过程如下:C>LINKMBAMicrosoft(R)OverlayLinkerVersion3.60Copyright(C)MicrosoftCorp1983-1987.Allrightsreserved.RunFile[MBA.EXE]:ListFile[NUL.MAP]:MBALibraries[.LIB]: 在连接程序调入后,首先显示版本号,然后出现三个提示行。第一个提示行:RunFile[MBA.EXE]:这是询问要产生的可执行文件的文件名。一般直接回车采用方括号内规定的隐含文件名。第二个提示行:ListFile[NUL.MAP]:这是询问是否要建立连接映象文件。若不建立,则直接回车;若要建立,则输入文件名再回车。我们在本例中要建立该文件,则输入文件名MBA。第三个提示行:Libraries[.LIB]:这是询问是否用到库文件。若无特殊需要,则直接回车即可。上述提示行回答后,连接程序开始连接。若连接过程中有错,则显示错误信息,错误分析清楚后,要重新调入编辑程序进行修改,然后重新汇编,再经过连接,直至无错为止。连接以后,便产生了可执行程序文件(EXE文件)。 汇编语言上机过程D:>EDITABC.asmD:>MASMABC;有语法错,回EDIT下改该程序D:>LINKABC;有错,回在EDIT下改程序D:>ABC运行结果错,回EDIT下改程序或在DEBUG下调试,找原因。D:>DEBUGABC.exe编辑源程序EDITABC.ASM汇编源程序MASMABC.ASM形成目标程序ABC.OBJ连接目标程序LINKABC.OBJ有连接错误信息?形成可执行程序ABC.EXE装入可执行程序到内存,并执行D:>sub>ABC下一程序用DEBUG调试可执行程序D:>sub>DEBUGABC.EXE找到原因YNYNNYN有汇编错误信息?Y运行结果正确?用DEBUG调试程序查错? 5.5.4程序的执行当我们建立了可执行文件MBA.EXE后,就可直接在DOS下执行该程序:C>MBAC>说明程序运行结束并返回到DOS。这里,我们并未看到运行结果,怎么知道程序运行已经结束?又怎么知道程序已返回DOS?下面我们来讨论这些问题。 5.5.5汇编语言和DOS操作系统的接口我们编写的汇编语言源程序要在DOS环境下运行时,必须先了解汇编语言是如何同DOS操作系统接口的。当我们用编辑程序把源程序输入到计算机中,用汇编程序把它转换为目标程序,用连接程序对其进行连接和定位时,操作系统为每一个用户程序建立了一个程序段前缀区PSP,其长度为256个字节,主要用于存放要执行程序的有关信息。同时,也提供了程序和操作系统的接口。操作系统在程序段前缀的开始处(偏移地址0000H)安排了一条INT20H软中断指令。INT20H中断服务程序由DOS提供,执行该服务程序后,控制就转移到DOS,即返回到DOS管理的状态。因此,用户在组织程序时,必须使程序执行完后能去执行存放于PSP开始处的INT20H指令,这样便返回到DOS,否则就无法继续键入命令和程序。 DOS在建立了程序段前缀区PSP之后,就将要执行的程序从磁盘装入内存。在定位程序时,DOS将代码段置于PSP下方,代码段之后是数据段,最后放置堆栈段。内存分配好之后,DOS就设置段寄存器DS和ES的值,以使它们指向PSP的开始处,即INT20H的存放地址。同时,将CS设置为PSP后面代码段的段地址,IP设置为指向代码段中第一条要执行的指令位置。把SS设置为指向堆栈的段地址,让SP指向堆栈段的栈底(取决于堆栈的长度),然后系统开始执行用户程序。为了保证用户程序执行完后能返回到DOS状态,可使用如下两种方法。 1.标准方法首先,将用户程序的主程序定义成一个FAR过程,其最后一条指令为RET。然后,在代码段的主程序(即FAR过程)的开始部分用如下三条指令将PSP中INT20H指令的段地址及偏移地址压入堆栈:PUSHDS;保护PSP段地址MOVAX,0;保护偏移地址0PUSHAX这样,当程序执行到主程序的最后一条指令RET时,由于该过程具有FAR属性,故存在堆栈内的两个字就分别弹出到CS和IP,从而执行INT20H指令,使控制返回到DOS状态。 例:建立一个多字节相加的汇编语言源程序。采用标准方法使程序被控制返回到DOS状态(返回DOS的标志就是程序运行完后出现一个DOS的标识符C>)。………………CODESEGMENT;定义代码段ASSUMECS:CODE,DS:DATA,SS:STACKMAINPROCFAR;主程序部分START:PUSHDS;将DS压入堆栈保存MOVAX,0PUSHAX;将0压入堆栈保存MOVAX,DATA;把数据段地址送AXMOVDS,AX;然后通过AX送入DSMOVAX,STACK;把堆栈段地址送AXMOVSS,AX;然后通过AX送入SS………… 2.非标准方法也可在用户的程序中不定义过程段,只在代码段结束之前(即CODEENDS之前)增加两条语句:MOVAH,4CHINT21H则程序执行完后也会自动返回DOS状态。此外,由于开始执行用户程序时,DS并不设置在用户的数据段的起始处,ES同样也不设置在用户的附加段起始处,因此,在程序开始处(或在保护了PSP段地址和偏移地址0以后),应该使用以下方法重新装填DS和ES的值使其指向用户的数据段:MOVAX,段名MOV段寄存器名,AX;段寄存器名可以是DS、ES、SS之一。 例dataSEGMENTmaxDB100HDUP(?)dataENDScodeSEGMENTASSUMECS:code,DS:datastart:MOVAX,data;置缓冲区地址于DS:DXMOVDS,AXLEADX,maxMOVAH,0AH;调输入功能INT21HMOVAH,4CH;返回DOSINT21HcodeENDSENDstart 5.5.6常用系统功能调用和BIOS中断调用微型计算机系统为汇编用户提供了两个程序接口:DOS系统功能调用ROM中的BIOS(BasicInput/OutputSystem)。DOS系统功能调用和BIOS由一系列的服务子程序构成,但调用与返回不是使用子程序调用指令CALL和返回指令RET,而是通过软中断指令INTn和中断返回指令IRET调用和返回的。DOS系统功能调用和BIOS的服务子程序,使得程序设计人员不必涉及硬件就可以使用系统的硬件,尤其是对I/O端口的使用与管理。 BIOS和DOS中断子程层次特点用户程序磁盘管理模块(DOS内核)MSDOS.SYS系统功能基本输入/输出BIOS模块IO.SYS设备驱动ROMBIOS基本I/O系统硬件装入命令处理模块COMMAND.COM用户命令DOS系统层次结构 1.系统功能调用系统功能调用是微机的磁盘操作系统DOS为用户提供的一组例行子程序,因而又称为DOS系统功能调用。DOS为用户提供了许多功能调用,这些子程序可分为以下四个主要方面:(1)磁盘的读/写及控制管理。(2)内存管理。(3)基本输入/输出管理(如键盘、打印机、显示器等)。(4)其他管理(如时间、日期等)。 为了使用方便,DOS系统已将所有功能子程序按顺序编号,称为调用号(或功能号)。其调用号范围为0H~75H,如表5.4所示。表中只列出了基本输入/输出管理中的部分键盘和显示器的DOS功能调用。对于所有的功能调用,使用时一般需要经过以下三个步骤:(1)子程序的入口参数送相应的寄存器。(2)子程序编号送AH。(3)发出中断请求:INT21H(系统功能调用指令)。子程序调用结束后,一般都有出口参数,出口参数放在寄存器中。通过检查出口参数,用户可以知道调用是否成功。常用的DOS系统功能调用是:1、2、9、10等。 表5.4键盘和显示器的DOS调用调用号功能入口参数出口参数1键入并显示一个字符键入字符的ASCII码在AL中2显示器显示一个字符DL中置输出字符的ASCII码5打印机打印一个字符DL中置输出字符的ASCII码8键盘输入一个字符键入字符的ASCII码在AL中9显示器显示一个字符串DS:DX置字符串首址,字符串以'$'结束10(0AH)键入并显示字符串DS:DX置字符串首址,第1单元置允许键入的字符数(含一个回车符)键入的实际字符数在第2单元中,键入的字符从第3单元开始存放11(0BH)检测有无键入有键入AL=FFH,无键入AL=0 调用方法设置入口参数在AH设置功能号m执行中断指令INTN分析、应用出口参数 1)1号功能调用——带显示的键盘输入入口参数:无调用格式:MOVAH,1INT21H出口参数:AL中为输入字符的ASCII码系统功能:扫描键盘,等待从键盘输入一个字符。一旦有键按下,就将输入字符的键值(相应字符的ASCII码值)读入到AL,并在屏幕上显示输入的字符。扫描键盘时,先检查输入字符是否为Ctrl–Break组合键。若是,则中断程序执行,返回DOS;若不是,则将键值送入AL寄存器,同时将这个字符显示在屏幕上。 2)2号功能调用——显示字符入口参数:DL寄存器的内容为要显示字符的ASCII码调用格式:MOVDL,待显示字符的ASCII码MOVAH,2INT21H出口参数:无系统功能:将DL寄存器中的字符送显示器输出,光标随动本调用执行后,显示器显示其ASCII码值放入DL中的字符。 DATASEGMENTBUFDB'HOWDOYOUDO?$'DATAENDSCODESEGMENTMOVAX,DATAMOVDS,AXMOVDX,OFFSETBUFMOVAH,9INT21HCODEENDS……… 例使光标回到下一行的行首。MOVDL,0DH;显示回车符MOVAH,02HINT21HMOVDL,0AH;显示换行符MOVAH,02HINT21H 3)9号功能调用——字符串显示入口参数:DX寄存器的内容,为要显示字符串的首地址调用格式:MOVDX,待显示字符串首字符的偏移地址MOVAH,9INT21H出口参数:无系统功能:显示以“$”为结束标志的字符串,并且字符串应在数据段中。遇’$’停止显示,光标随动。本调用执行后,显示器显示待显示的字符串。调用时,要求DS:DX必须指向内存中一个以“$”作为结束标志的字符串。例: 例编程显示字符串’Hello!’dataSEGMENT;定义显示的子符串striDB‘Hello’,‘$’dataENDScodeSEGMENTASSUMECS:code,DS:datastart:MOVAX,data;置缓冲区地址于DS:DXMOVDS,AXLEADX,striMOVAH,09H;调显示功能INT21HMOVAH,4CH;返回DOSINT21HcodeENDSENDstart 4)10号功能调用——字符串输入入口参数:DX寄存器的内容为指定内存数据区首地址调用格式:MOVDX,数据区的首偏移地址MOVAH,10INT21H出口参数:无系统功能:将从键盘接收的字符串送到内存数据区,直到输入回车符为止。光标移动。为此,要事先定义一个数据区,在数据区内:第一个字节指出数据区能容纳的字符个数,不能为零;第二个字节保留,以用做填写实际输入的字符个数;从第三个字节开始,存放从键盘上接收的字符串。若实际输入的字符数少于定义的字节数,则数据区内其余字节填零;若多于定义的字节数,则后来输入的字符丢掉,且响铃报警。调用时,要求DS:DX指向数据区首地址。例如: DATASEGMENTBUFDB50;数据区长度DB?;保留,填入实际输入的字符个数DB50DUP(?);定义50个字节存储空间DATAENDSCODESEGMENTMOVDX,OFFSETBUFMOVAH,10INT21HCODEENDS…… DOS功能调用程序段:例1单字符显示功能调用MOVDL,‘A’;设置入口参数MOVAH,02H;设置功能号INT21H;执行中断调用,显示‘A’例2单字符输入功能调用MOVAH,01H;设置功能号INT21H;执行中断调用,显示键盘输入CMPAL,0DH;分析、应用出口参数JZexit… 例3:显示一个字符串'Goodmorning!‘的程序如下:MSGDB‘Goodmorning!$‘;单引号内是ASCII码,$表示字符结束MOVDX,OFFSETMSG;字符串首字符的偏移地址送DXMOVAH,9;功能号9送AHINT21H;系统功能调用,显示字符串有的子程序不需要入口参数,这时步骤(1)可以略去。例4:MOVAH,4CH;功能号4CH送AH(返回DOS子程序)INT21H;非标准返回… 例5应用0AH功能输入字符串。dataSEGMENT;定义缓冲区maxDB11;定义限制最多输入个数lenthDB?;用于存放实际输入个数striDB11DUP(?);用于存放输入的字符串dataENDScodeSEGMENTASSUMECS:code,DS:datastart:MOVAX,data;置缓冲区地址于DS:DXMOVDS,AXLEADX,maxMOVAH,0AHINT21H;调0A输入功能MOXCH,0MOVCL,lenth;取字符串长度放CX中LEABX,stri;取字符串首址于BX中MOVAL,[BX];应用输入字符、、、、、codeENDS dataSEGMENT;定义缓冲区maxDB11;定义限制最多输入个数lenthDB?;用于存放实际输入个数striDB11DUP(?);用于存放输入的字符串dataENDS从键盘输入‘ABCD’,回车,内存的存放结果:0B04414243440D00max07000000000000000080a0b0f12345690c0d0elenthstri地址内容DS:DX 如下编程与上例相同:dataSEGMENTmaxDB11等价于:DB?maxDB11,?,11DUP(?)DB11DUP(?)dataENDScodeSEGMENTASSUMECS:code,DS:datastart:MOVAX,data;置缓冲区地址于DS:DXMOVDS,AXLEADX,maxMOVAH,0AH;调输入功能INT21HMOXCH,0MOVCL,max+1;取字符串长度放CX中LEABX,max+2;取字符串首址于BX中MOVAL,[BX];应用输入字符、、、codeENDS dataSEGMENTmaxDB11DB?DB11DUP(?)dataENDS从键盘输入‘ABCD’,回车,内存的存放结果:等价于maxDB11,?,11DUP(?)0B04414243440D00max07000000000000000080a0b0f12345690c0d0e地址内容DS:DX 0AH功能执行过程:①若(DS:DX)字节单元的值为0,则不等待从键盘输入,结束调用。②若(DS:DX)字节单元的内容大于0,则等待从键盘输入,并把输入键的ASCII码顺序存放在DS:DX+2开始的单元,按回车键表示结束输入。当按下键的个数超过(DS:DX)中值,发出警告声’嘟嘟’,不再接收输入的数据,直到输入回车键。③将实际输入的字符个数(不包括回车键)填入(DS:DX+1),结束调用。DS:DX00000000000000000000000000000000 0AH功能注意事项:◢输入的字符均带回显,且光标随字符移动。当输入回车符结束时,也回显回车符。表现为功能调用结束后,光标回到了行首。◢回车符0DH作为一个输入的字符存放在字符串尾,但计数输入个数时,不包括回车键。实际最多能输入的字符数=限制的最多数-1(回车符占一个)◢执行完0AH功能后,DS和DX的值不变,DS:DX仍指向缓冲区的首地址。◢整个缓冲区的大小应为:限制的最多数+2maxDB11,?,11dup(?) 2.常用系统功能调用应用举例例5.5利用DOS系统功能调用实现人机对话。下述程序可以在屏幕上显示一行提示信息,然后接收用户从键盘输入的信息并将其存入内存数据区。DATASEGMENTPARSDB100;定义输入缓冲区DB?DB100DUP(?)MESGDB'WHATISYOURNAME?';要显示的提示信息DB'$';提示信息结束标志 DATAENDSSTACKSEGMENTPARASTACK'STACK'DB100DUP(?)STACKENDSCODESEGMENTASSUMECS:CODE,DS:DATA,SS:STACKSTARTPROCFARBEGIN:PUSHDSMOVAX,0PUSHAXMOVAX,DATAMOVDS,AX DISP:MOVDX,OFFSETMESGMOVAH,9;利用9号功能调用显示提示INT21HKEYBD:MOVDX,OFFSETPARSMOVAH,10;利用10号功能调用接收键盘输入INT21HRETSTARTENDPCODEENDSENDBEGIN 例5.6编写汇编语言源程序,将键入的4位十进制数(如5,则键入0005)以压缩BCD数形式存入字变量SW中。该程序首先接收键入的4位十进制数,然后拼合为压缩BCD数,存入字变量SW。为了接收键入的4位十进制数,需要在数据段中定义一变量数据区。该数据区应有7个字节,其中第1字节定义为5,即可接收5个字符,第2字节预留给10号功能调用装载实际键入的字符数,第3字节到第7字节预留给10号功能调用装载实际键入的字符,即4字节十进制数的ASCII码和1字节回车的ASCII码。程序如下: 请仔细阅读和理解程序!DATASEGMENTBUFDB5,0,5DUP(?)SWDW?DATAENDSCODESEGMENTBEGINPROCFARASSUMECS:CODE,DS:DATA START:PUSHDSSUBAX,AXPUSHAXMOVAX,DATAMOVDS,AXMOVDX,OFFSETBUF;10号功能调用,键入4位十进制数MOVAH,10INT21HMOVAX,WORDPTRBUF+4;键入数的个位和十位送AXANDAX,0F0FH;将两个ASCII码转为非压缩BCD数 MOVCL,4SHLAL,CL;将十位移至AL的高4位ORAL,AH;十位和个位拼合在AL中MOVBYTEPTRSW,AL;存BCD数的十位和个位MOVAX,WORDPTRBUF+2;键入数的百位和千位送AXANDAX,0F0FH;将两个ASCII码转为非压缩BCD数SHLAL,CL;将千位移至AL的高4位ORAL,AH;千位和百位拼合在AL中MOVBYTEPTRSW+1,AL;存BCD数的千位和百位RETBEGINENDPCODEENDSENDSTART MOVCL,4SHLAH,CL;将十位移至AL的高4位ORAL,AH;十位和个位拼合在AL中MOVBYTEPTRSW,AL;存BCD数的十位和个位MOVAX,WORDPTRBUF+2;键入数的百位和千位送AXANDAX,0F0FH;将两个ASCII码转为非压缩BCD数SHLAH,CL;将千位移至AL的高4位ORAL,AH;千位和百位拼合在AL中MOVBYTEPTRSW+1,AL;存BCD数的千位和百位RETBEGINENDPCODEENDSENDSTART 3.BIOS中断调用BIOS是固化在ROM中的一组I/O驱动程序,它为系统各主要部件提供设备级控制,还为汇编语言程序设计者提供了字符I/O操作。与DOS功能调用相比,BIOS有如下特点:① 调用BIOS中断程序虽然比调用DOS中断程序要复杂一些,但运行速度快,功能更强;② DOS的中断功能只是在DOS环境下适用,而BIOS功能调用不受任何操作系统的约束;③某些功能只有BIOS具有。 1)键盘服务程序键盘服务程序的中断类型号为16H,用INT16H调用。软中断INT16H服务程序有三个功能,功能号分别为0、1、2,功能号及出口参数如表5.5所示。表5.5INT16H的功能功能号功能出口参数0从键盘读字符键入字符的ASCII码在AL中1检测键盘是否键入字符键入字符ZF=0,未键入字符ZF=12读键盘各转换键的当前状态各转换键的状态在AL中 2)打印机服务程序打印机服务程序的中断类型号为17H,用INT17H调用。软中断INT17H服务程序有三个功能,功能号为0、1、2,其中打印一字符的功能号为0,入口参数是将打印字符的ASCII码送AL,打印机号0~2送DX。 3)显示器服务程序显示器服务程序的中断类型号为10H,用INT10H调用。软中断INT10H服务程序有16个功能,功能号为0~15。常用功能如表5.6所示。表5.6中的“显示方式”有7种,如表5.7所示。表5.6中的显示属性是字符方式下字符的显示属性,由一个字节定义,由它来设置字符和背景的颜色。显示属性字节如图5.5所示。其中背景颜色和字符颜色如表5.8所示。 表5.6INT10H的功能 图5.5显示属性字节 表5.7显示方式AL值显示方式040×25黑白字符方式140×25彩色字符方式280×25黑白字符方式380×25彩色字符方式4320×200黑白图形方式5320×200彩色图形方式6640×200黑白图形方式780×25单色字符方式 表5.8字符颜色RGB背景色或正常亮度字符色高亮度字符色000黑灰001蓝浅蓝010绿浅绿011青蓝浅青蓝100红浅红101品红浅品红110棕黄111白高亮度白 黑白字符方式下字符的显示属性仅为黑或白(灰或高亮度白)。图形方式的颜色设置与字符方式不同,其颜色不用显式属性字节来设置。设置的方法是用功能号11设置背景颜色,用功能号11和12设置像点颜色。背景颜色有16种,编号为0~15,其颜色是彩色字符方式下正常亮度和高亮度字符颜色的组合,即0为黑色,1为蓝色,……,15为高强度白色。像点的颜色只有6种,由彩色组和彩色值来选择,如表5.9所示。表5.9像点颜色彩色值彩色组0彩色组11绿青2红品红3黄白 例5.7在屏幕的13行40列位置显示高亮度闪动的“太阳”。程序如下:STACKSEGMENTSTACK'STACK'DW32DUP(?)STACKENDSCODESEGMENTASSUMESS:STACK,CS:CODESTART:MOVAX,STACKMOVSS,AXMOVAH,7;80×25单色字符方式MOVAL,2INT10H MOVAH,15;读取显示页号INT10HMOVAH,2;设置光标位置MOVDX,0D28HINT10HMOVAH,9;高亮度白闪烁的太阳MOVAL,0FHMOVBL,8FHMOVCX,1INT10HMOVAH,4CH;返回DOSINT21HCODEENDSENDSTART 5.6汇编语言程序设计的基本方法1.编写汇编语言程序步骤分析实际问题,抽象描述问题的模型确定解决模型的算法按算法画出程序流程图按流程图编写程序上机调试,运行程序2.判断程序质量的标准程序的正确性程序的可读性程序的执行时间程序所占内存大小 3.几种程序结构顺序结构分支结构循环结构子程结构(1)顺序结构程序 两个分支YN…CMPAL,BLJGgreatJMPexitgreat:…exit:……AL≤BL处理AL>BL处理(2)分支结构程序 三个分支…CMPAL,0JGgreatJLlessJMPexitless:JMPexitgreat:…exit:…AL=0处理AL>0处理AL<0处理YYNN(2)分支结构程序 (3)循环结构程序当型循环(当条件成立进入循环)循环初始设置循环体循环条件判断?YN直到型循环(直到条件成立退出循环)YN循环初始设置循环体循环条件判断? 5.6.1顺序程序设计顺序程序是一种最简单的程序,也称为直线程序,它的执行自始至终按照语句出现的先后顺序进行。例5.8求两个无符号数(95和87)的平均值。这两个数分别放在存储器的x单元和y单元中,其平均值放在z单元中。程序如下:DATASEGMENTxDB95yDB87zDB?DATAENDS CODESEGMENTMAINPROCFAR;过程定义,属性为FARASSUMECS:CODE,DS:DATASTART:PUSHDS;保护PSP段地址MOVAX,0;保护偏移地址0PUSHAX;标准方法返回DOSMOVAX,DATA;装填数据段寄存器DSMOVDS,AX;建立数据段基地址 MOVAL,x;第一个数送入ALADDAL,y;两数相加,结果送ALMOVAH,0;为求两数平均值(除2)作准备ADCAH,0;带进位加法,使进位位送到AHMOVBL,2;除数(2)送BLDIVBL;求平均值送ALMOVz,AL;结果送入z单元RET;堆栈弹出CS和IP,执行INT20HMAINENDP;返回DOSCODEENDSENDSTART 例5.9在内存中自tab开始的16个单元连续存放着0~15的平方值(平方表),任给一个数x(0≤x≤15),如x=13,且将13存放在x单元中,查表求x的平方值,并把结果送入y单元中。根据给出的平方表,分析表的存放规律,可知表的起始地址与数x之和,正是x的平方值所在的存储单元的地址,由此编制程序如下:DATASEGMENTtabDB0,1,4,9,16,25,36,49,64,81DB100,121,144,169,196,225xDB13yDB?DATAENDS CODESEGMENTASSUMECS:CODE,DS:DATASTART:MOVAX,DATAMOVDS,AXLEABX,tabMOVAH,0MOVAL,xADDBX,AXMOVAL,[BX]MOVy,ALMOVAH,4CHINT21HCODEENDSENDSTART 5.6.2分支程序设计顺序程序的特点是从程序的第一条指令开始,按顺序执行指令,直到执行完最后一条指令为止。然而,许多实际问题并不能设计成顺序程序,需要根据不同的条件作出不同的判断和处理。把不同的处理方法编制成各自的处理程序段,运行时由机器根据不同的条件自动作出选择判断,绕过某些指令,仅执行相应的处理程序段。按这种方式编制的程序,执行的顺序与指令存储的顺序失去了完全的一致性,称之为分支程序。分支程序是机器利用改变标志位的指令和转移指令来实现的。 转移指令有JMP和Jcc两类。前者是无条件转移指令,后者是条件转移指令。JMP无条件转移指令将控制转向其后的目的标号指定的地址。Jcc条件转移指令跟随在能改变状态标志的指令之后,根据条件决定是否将控制转向其后的目的地址处。 例5.10给定以下符号函数:任意给定x值,假定为−25,且存放在x单元,函数值y存放在y单元,根据x的值确定函数y的值。程序流程图如图5.6所示。 图5.6实现符号函数程序的流程图 程序如下:DATAXSEGMENTxDB−25yDB?DATAXENDSCODEXSEGMENTMAINPROCFARASSUMECS:CODEX,DS:DATAX START:PUSHDSMOVAX,0PUSHAXMOVAX,DATAXMOVDS,AXMOVAL,x;AL←xCMPAL,0JGELOOP1;x≥0时转LOOP1MOVAL,0FFH;否则将−1送入y单元MOVy,ALRET LOOP1:JELOOP2;x=0时转LOOP2MOVAL,1;否则将1送入y单元MOVy,ALRETLOOP2:MOVAL,0;将0送入y单元MOVy,ALRETMAINENDPCODEXENDSENDSTART 例5.11设有首地址为arry的字数组,已按升序排好,数组长度为n(假设n=15),且数据段与附加段占同一段,在该数组中查找数number(假设等于83)。若找到它,则从数组中删掉;若找不到,则把它插入正确位置,且变化后的数组长度在DX中。根据题意编写程序如下:DATAJSEGMENTDW?nDW15numberDW83arryDW5,10,17,21,28,32,41,50,56,67,72DW88,95,125,150DATAJENDS CODMASEGMENTMAINPROCFARASSUMECS:CODMA,DS:DATAJ,ES:DATAJSTART:PUSHDSSUBAX,AXPUSHAXPUSHESMOVAX,DATAJMOVDS,AXPUSHDS POPESMOVAX,number;待查找的数放入AXMOVDX,n;初始化DXMOVCX,n;设置计数器CXMOVDI,OFFSETarry;arry的有效地址放入DICLD;建立方向标志REPNESCASW;用重复串扫描指令进行查找JEDELETEDECDXMOVSI,DXADDSI,DX TT3:CMPAX,arry[SI]JLTT1MOVarry[SI+2],AX;功能是:若没有查到,JMPTT2;则将此数插入正确位置TT1:MOVBX,arry[SI]MOVarry[SI+2],BXSUBSI,2JMPTT3TT2:ADDDX,2;修改数组长度JMPFANDELETE:JCXZNEXT LOOPT:MOVBX,[DI];此程序段功能是:若查找到,MOV[DI−2],BX;则从数组中删除该数ADDDI,2LOOPLOOPTNEXT:DECDX;修改数组长度FAN:POPESRETMAINENDPCODMAENDSENDSTART 5.6.3循环程序设计1.循环程序的结构图5.7循环程序的基本结构(a)先执行后判断;(b)先判断后执行 (1)初始化部分:建立循环初始值。如设置地址指针、计数器、其他循环参数的起始值等。(2)工作部分:在循环过程中所要完成的具体操作,是循环程序的主要部分。这部分视具体情况而定。它可以是一个顺序程序、一个分支程序或另一个循环程序。(3)修改部分:为执行下一个循环而修改某些参数。如修改地址指针、其他循环参数等。 (5)结束处理部分:对循环结束进行适当处理,如存储结果等。有的循环程序可以没有这部分。图5.7(a)给出的循环程序框图是“先执行后判断”的结构;另有一种结构形式是“先判断后执行”的形式,如图5.7(b)所示,这种结构仍由五个部分组成,但是重新安排了中间三个部分的顺序。从框图可以看出它的最大优点是可以一次也不执行循环,也就是说可以设计为零次循环的程序。 2.循环控制方法1)用计数控制循环这种方法直观、方便,易于程序设计。只要在编制程序时,循环次数已知,就可以使用这种方法设计循环程序。例5.12从xx单元开始的30个连续单元中存放有30个无符号数,从中找出最大者送入yy单元中。根据题意,我们把第一个数先送入AL寄存器,将AL中的数与后面的29个数逐个进行比较。如果AL中的数较小,则两数交换位置;如果AL中的数大于等于相比较的数,则两数不交换位置。在比较过程中,AL中始终保持较大的数,比较29次,则最大者必在AL中。最后把AL中的数(最大者)送入yy单元。 图5.8从一批数中求最大者的程序流程图 程序如下:DATASPSEGMENTxxDB73,59,61,45,81DB107,37,25,14,64DB3,17,9,23,55,97DB115,78,121,67DB215,137,99,241DB36,58,87,100,74,62yyDB? DATASPENDSCODESPSEGMENTASSUMECS:CODESP,DS:DATASPMAINPROCFARSTART:PUSHDSMOVAX,0PUSHAXMOVAX,DATASPMOVDS,AXMOVAL,xxMOVSI,OFFSETxxMOVCX,29 LOOP1:INCSICMPAL,[SI]JAELOOP2XCHGAL,[SI]LOOP2:DECCXJNZLOOP1MOVyy,ALRETMAINENDPCODESPENDSENDSTART 2)用条件控制循环有些情况无法确定循环次数,但可用某种条件来确定是否结束循环。这时,编制程序主要是寻找控制条件以及对控制条件的检测。例5.13从自然数1开始累加,直到累加和大于1000为止,统计被累加的自然数的个数,并把统计的个数送入n单元,把累加和送入sum单元。根据题意,被累加的自然数的个数事先是未知的,也就是说,循环的次数是未知的,因此不能用计数器方法控制循环。但题目中给定一个重要条件,即累加和大于1000则停止累加,因此,可以根据这一条件控制循环。我们用CX寄存器统计自然数的个数,用AX寄存器存放累加和,用BX寄存器存放每次取得的自然数。程序的流程图如图5.9所示。 图5.9利用条件控制循环的程序流程图 程序如下:DATASSEGMENTnDW?sumDW?DATASENDSSTACKSEGMENTPARASTACK'STACK'DW200DUP(?)STACKENDSCODESSEGMENT MAINPROCFARASSUMECS:CODES,DS:DATAS,SS:STACKSTART:PUSHDSMOVAX,0PUSHAXMOVAX,DATASMOVDS,AXMOVAX,0MOVBX,0MOVCX,0 LOOPT:INCBXADDAX,BXINCCXCMPAX,1000JBELOOPTMOVn,CXMOVsum,AXRETMAINENDPCODESENDSENDSTART 5.6.4子程序设计1.子程序概念如果在一个程序中的多处需要用到同一段程序,或者说在一个程序中需要多次执行某一连串的指令时,那么我们可以把这段要执行的程序或这一连串的指令抽取出来,写成一个相对独立的程序段,每当我们想要执行这段程序或这一连串的指令时,就调用这段程序,执行完这段程序后再返回原来调用它的程序。这样我们每次执行这段程序时,就不必重写这一连串的指令了,这样的程序段称为子程序或过程。而调用子程序的程序称为主程序或调用程序。 2.子程序的定义子程序是用过程定义伪指令PROC和ENDP来定义的,而且还应指出过程的类型属性。在PROC和ENDP之间是为完成某一特定功能的一连串指令,其最后一条指令是返回指令RET。过程通常以一个过程名(标号)后跟PROC开始,而以过程名后跟ENDP结束。其格式如下:过程名PROC[NEAR/FAR]RET过程名ENDP… 其中:‘过程名’是子程序入口的符号地址;NEAR或FAR是过程的类型属性,它指出对该过程的调用是段内调用还是段间调用,NEAR用于段内调用,而FAR用于段间调用。过程属性的确定原则为:①调用程序和过程若在同一代码段中,则使用NEAR属性;②调用程序和过程若不在同一代码段中,则使用FAR属性;③主程序应定义为FAR属性(使用标准方式返回DOS时)。因为我们把程序的主过程看作DOS调用的一个子程序,而DOS对主过程的调用和返回都是FAR属性。另外,过程定义允许嵌套,即在一个过程定义中允许包含多个过程定义。 例5.14调用程序和子程序在同一代码段中。CODESEGMENTMAINPROCFARCALLPPP1RETPPP1PROCNEAR………… CALLPPP2RETPPP2PROCNEARRETPPP2ENDPPPP1ENDPMAINENDPCODEENDS…… 例5.15调用程序和子程序不在同一代码段。CODE1SEGMENTCALLRRRCODE1ENDSCODE2SEGMENTRRRPROCFARRETRRRENDPCODE2ENDS………… 1.多处调用完成同一功能的子程序:codeSEGMENTstart:…CALLsub…CALLsub…CALLsub…MOVAH,4CHINT21HsubPROC……RETsubENDPcodeENDSENDstart2.模块化程序设计:codeSEGMENTbegin:CALLsub1CALLsub2CALLsub3MOVAH,4CHINT21Hsub1PROC…RETsub1ENDPsub2PROC…RETsub2ENDPsub3PROC…RETsub3ENDPcodeENDSENDbegin子程序结构注意返回DOS语句位置 程序的执行流程:codeSEGMENTstart:…CALLsub…CALLsub…;MOVAH,4CH;INT21HsubPROC……RETsubENDPMOVAH,4CHINT21HcodeENDSENDstart程序执行不到返回DOS功能调用处,最后的结果是死机. 3.调用程序与子程序之间的参数传递调用程序在调用子程序时,往往需要向子程序传递一些参数;同样,子程序运行后也经常要把一些结果参数传回给调用程序。调用程序与子程序之间的这种信息传递称为参数传递。参数传递有以下三种主要方式。1)通过寄存器传递参数这种方式适合于传递参数较少的一些简单程序。 例5.16把一个2位十进制数表示成的压缩型BCD数转换成与其对应的二进制数。在调用子程序前,首先把待转换的压缩型BCD数放在寄存器AL中,然后由主程序传递给子程序BCD_BINARY,子程序结束时把转换的二进制结果传递回调用程序,并保存在VALUE单元中。在子程序一开始,把标志寄存器及其他在程序中用到的寄存器压入堆栈,但AX寄存器不需要压入和弹出堆栈,因为我们是用AX寄存器把一个值传递到子程序中,同时子程序把另一个不同的值放在AX寄存器中,从而传回给主程序。程序如下: DATA_BINSEGMENTBCD_INDB?;存放BCD值VALUEDB?;存放二进制值DATA_BINENDSCODESEGMENTASSUMECS:CODE,DS:DATA_BINMAINPROCFARSTART:PUSHDSMOVAX,0PUSHAX MOVAX,DATA_BINMOVDS,AXMOVAL,BCD_INCALLBCD_BINARYMOVVALUE,ALRETMAINENDPBCD_BINARYPROCNEARPUSHF;保存标志寄存器FLAGSPUSHBX;保存BX和CXPUSHCXMOVAH,AL;把BCD数送入AHANDAH,0FH MOVBL,AH;保存BCD数的低位数字ANDAL,0F0H;分出BCD数的高位数字MOVCL,04RORAL,CL;把BCD数高位数字移到低位MOVBH,0AH;把转换因子送入BHMULBH;AL中BCD数的高位数字乘以BH中;的0AH,结果在AX中ADDAL,BLPOPCX;把相乘结果与BCD码的低位数字相加,;最终结果送入AL中POPBXPOPF;恢复被保护的寄存器RETBCD_BINARYENDPCODEENDSENDSTART 2)通过地址表传递参数地址这种方式适合于参数较多的情况,但要求事先建立一个用来传送参数的地址。例5.17对例5.16通过传送参数地址的方法重新编写程序如下:DATASEGMENTBCD_INDB?VALUEDB?DATAENDSCODESEGMENTASSUMECS:CODE,DS:DATAMAINPROCFAR START:PUSHDSMOVAX,0PUSHAXMOVAX,DATAMOVDS,AXMOVSI,OFFSETBCD_INMOVDI,OFFSETVALUECALLBCD_BINARYMOV[DI],ALRET MIANEMDPBCD_BINARYPROCNEARPUSHFPUSHBXPUSHCXMOVAL,[SI];把BCD_IN单元中的数送入ALMOVAH,ALANDAH,0FHMOVBL,AHANDAL,0F0HMOVCL,4RORAL,CL MOVBH,0AHMULBHADDAL,BLPOPCXPOPBXPOPFRETBCD_BINARYENDPCODEENDSENDSTART 3)通过堆栈传递参数为了利用堆栈传递参数,必须在主程序中调用子程序之前的地方,把这些参数压入堆栈,然后利用在子程序中的指令从堆栈弹出而取得参数。同样,要从子程序传递回调用程序的参数也被压入堆栈内,然后由主程序中的指令把这些参数从堆栈中取出。下面举例说明如何利用堆栈来传递参数。 例5.18试将一个2位十进制数的压缩型BCD码转换成十六进制数,并在屏幕上显示出来。根据题目的要求,我们利用堆栈传递参数的方法。首先在主程序中把BCD码压入堆栈,在子程序BCD_BIN中从堆栈中取出这个BCD码,然后先把它转换成二进制数,并将其保存在堆栈。返回主程序后再将这个二进制数从堆栈中取出,再把它转换成十六进制数,最后把这个十六进制数在屏幕上显示出来。程序如下: DATAHSEGMENTBCDMADB?DATAHENDSSTACKSEGMENTPARASTACK'STACK'DW100DUP(?)TOSLABLEWORDSTACKENDSCODEHSEGMENT ASSUMECS:CODEH,DS:DATAH,SS:STACKMAINPROCFARSTART:MOVAX,STACKMOVSS,AXMOVSP,OFFSETTOSPUSHDSMOVAX,0PUSHAXMOVAX,DATAHMOVDS,AXMOVAH,00HMOVAL,BCDMA PUSHAX;把BCD码压入堆栈CALLBCD_BIN;调用子程序把BCD码转化成二进制数POPAX;把转化后的二进制数弹出送入AXMOVCH,2LOOP1:MOVCL,4ROLAL,CLMOVBL,ALANDBL,0FHADDBL,30HCMPBL,3AHJLLOOP2ADDBL,07H LOOP2:MOVDL,BLMOVAH,02HINT21HDECCHJNZLOOP1RETMAINENDPBCD_BINPROCNEARPUSHAXPUSHFPUSHBXPUSHCXPUSHBPMOVBP,SP MOVAX,[BP+12]MOVAH,ALANDAH,0FHMOVBL,AHANDAL,0F0HMOVCL,4RORAL,CLMOVBH,0AHMULBHADDAL,BL MOV[BP+12],AXPOPBPPOPCXPOPBXPOPFPOPAXRETBCD_BINENDPCODEHENDSENDSTART 利用堆栈传递参数有两个非常重要的问题:当使用堆栈来传递参数时,有一个应当注意的潜在问题就是堆栈溢出(StackOverflow)。所谓堆栈溢出,是指堆栈超出了为它开辟的存储空间。每当用堆栈传送参数时,应当非常清楚已经把什么东西压入了堆栈内,在子程序中每个地方的堆栈指针指向了哪里,这对编写程序的工程技术人员来说是非常重要的。如果稍不注意,很容易搞乱程序,并造成堆栈溢出,给程序调试带来很多麻烦。 (2)8086/8088有四种形式的RET指令:一般的近程RET指令能把返回地址由堆栈弹入到IP,同时把堆栈指针加2;一般的远程RET指令能由堆栈把返回的IP及CS值弹入到IP及CS,同时把堆栈指针加4,其他两种RET指令形式分别执行相同的功能,但是它们会把一个在指令中指定的数字加入堆栈指针。例:近程RET6指令会从堆栈弹出一个字的内容到IP,同时把堆栈指针加2,然后再加6到堆栈指针。这是一种快速方式,可以让堆栈指针往下(地址增大方向)跳过一些参数。 图5.10例5.17的堆栈示意图 子程与主程的参数传送小结:用寄存器传送(如,用BX寄存器传送参数)用定义的变量传送(如,用变量num+2传送参数)3)利用地址表传送4)用堆栈传送编写子程序的注意事项:注意子程序中PUSH、POP应成对,否则易造成死机。 dataSEGMENTstringDB‘Hello’,’$’dataENDScodeSEGMENTASSUMECS:code,DS:datastart:MOVAX,dataMOVDS,AXCALLinputMOVAH,4CHINT21HinputPROCPUSHAXLEADX,stringMOVAH,09HINT21HRETinputENDPcodeENDSENDstart执行call前SS:SP(AX)执行call后SS:SP(IP)执行push后SS:SP执行ret后SS:SP(IP) 例程序段是否可以完成AX→CX,BX→DXCODESEGMENTASSUMECS:codestart:MOVAX,dataMOVDS,AXPUSHAXPUSHBXCALLsubMOVAH,4CHINT21HsubPROCPOPDXPOPCX……RETsubENDPcodeENDSENDstartpushax前SS:SP(BX)pushax后SS:SP(AX)pushbx后SS:SP执行ret后SS:SP(IP)callsub后SS:SPSS:SPpopdx后SS:SPpopcx后(DX)(CX) 4.子程序的嵌套一个子程序可以作为调用程序去调用别的子程序,这种结构称为子程序的嵌套。只要有足够的堆栈空间,嵌套的层数是不受限制的。嵌套层数称为嵌套深度。当调用程序去调用子程序时,将产生中断点,而子程序执行完后返回到调用程序的断点处,使调用程序继续往下执行。因此,对于嵌套结构,中断点的个数等于嵌套的深度,如图5.11所示。 图5.11子程序嵌套示意图 例5.19设有两个无符号数125和378,其首地址为x,求它们的和,将结果存放在SUM单元;并将其和转换为十六进制数且在屏幕上显示出来。根据题意,我们设计一个主程序MAIN和两个子程序PROCEDP和PROCEDX。在主程序中完成必要的初始化,然后调用子程序。在子程序PROCEDP中完成两个数的求和,并把求和结果送入指定单元。然后在子程序PROCEDP中再去调用子程序PROCEDX,把和数转化为十六进制数并在屏幕上显示出来。显然这是一个嵌套结构,其嵌套深度为2。程序如下: DATAPSEGMENTxDW125,378sumDW?DATAPENDSCODEPSEGMENTMAINPROCFARASSUMECS:CODEP,DS:DATAPSTART:PUSHDSXORAX,AXPUSHAXMOVAX,DATAP MOVDS,AXMOVSI,OFFSETx;把x的有效地址送入SICALLPROCDPRETMAINENDP;求和子程序PROCDPPROCNEARPUSHDX;寄存器保护PUSHBXPUSHAXPUSHSIPUSHCX MOVAX,[SI];把第一个数送入AXADDAX,[SI+2];两个数相加MOVsum,AX;把和送入指定单元CALLPROCDXPOPCX;寄存器恢复POPSIPOPAXPOPBXPOPDXRETPROCDPENDP ;十六进制转换子程序PROCDXPROCNEARMOVBX,sumMOVCH,4T1:MOVCL,4ROLBX,CL;循环左移4次MOVAL,BL;屏蔽高4位ANDAL,0FHADDAL,30H;转换为ASCII码CMPAL,3AH;ASCII码与3AH比较JLT2ADDAL,07H;ASCII码在A~F T2:MOVDL,AL;ASCII码在0~9MOVAH,2INT21H;DOS系统功能调用,显示一个字符DECCHJNZT1RETPROCDXENDPCODEPENDSENDSTART 5.7发挥80386及其后继机型的优势5.7.1充分利用高档机的32位字长特性80x86系列从80386起就把机器字长从16位增加到32位。字长的增加除有利于提高运算精度外,也能提高编程效率。例如,多字节(如双字长)数的加法,在8086中必须用ADD或ADC指令序列来完成;而在386及其后继机型中只用一条ADD指令就可完成双字长加法操作。因此,不论在空间方面还是时间方面都有利于程序效率的提高。 例5.20如有两个4字长(64位)数分别存放在data1和data2中,用8086指令编写一程序求出它们的和,并把结果存放于data3中。解:为了得到4字长数的和,在8086中需要分4段计算,每段一个字长(16位),用4次循环可得到4字长数的和。考虑到每次求和可能有进位值,要用ADC(而不是ADD)指令求和,而且在进入循环前应先清除CF位。 在循环中修改地址指针时用INC指令而不用ADD指令,以免影响求和时得到的进位值(INC指令不影响CF位)。程序如下: DATASEGMENTdata1DQ123456789ABCDEFHdata2DQ0FEDCBA987654321Hdata3DQ?DATAENDSCODESEGMENTSTART:MOVAX,DATAMOVDS,AXCLCLEASI,data1;data1偏移地址送SILEADI,data2;data2偏移地址送DILEABX,data3;data3偏移地址送BXMOVCX,4 BACK:MOVAX,WORDPTR[SI];第一个加数送AXADCAX,WORDPTR[DI];和第二个加数相加,并送到AXMOVWORDPTR[BX],AX;存和值INCSIINCSIINCDIINCDIINCBXINCBXLOOPBACKMOVAX,4C00H;返回DOSINT21HCODEENDSENDSTART 例5.21编制80386及其后继机型的程序,实现例5.20的要求。在386及其后继机型中可以充分利用其32位字长的特点,每次可对双字求和,这样循环2次就可得到4字长数之和。程序如下(由于循环次数的减少,速度上要优于例5.20的程序)。.386DATASEGMENTdata1DQ123456789ABCDEFHdata2DQ0FEDCBA987654321Hdata3DQ?DATAENDSCODESEGMENT START:MOVAX,DATAMOVDS,AXCLCLEASI,data1;data1偏移地址送SILEADI,data2;data2偏移地址送DILEABX,data3;data3偏移地址送BXMOVCX,2;设置循环次数CX=2BACK:MOVEAX,DWORDPTR[SI];第一个加数送EAXADCEAX,DWORDPTR[DI];和第二个加数相加,并送到EAXMOVDWORDPTR[BX],EAX;存和值 PUSHF;保存CFADDSI,4ADDDI,4ADDBX,4POPF;恢复CFLOOPBACKMOVAX,4C00H;返回DOSINT21HCODEENDSENDSTART 分析以上两例程序实现所需要的时钟周期数,可以得出这样的结论:用386或486运行上述程序,用32位字长计算可获得比用16位字长计算快5~7倍的效果。可见,尽可能利用高档机的32位字长特性是很有意义的。这里只是以加法运算为例,说明利用高档机32位字长特性的重要性。实际上,对于其他指令也有类似的效果。对乘/除法等更复杂的指令,收到的效果会更好。 上面所说的充分利用高档机的32位字长的特性,当然也包括对其提供的32位寄存器在内。386及其后继机型除可访问8086、80286所提供的8位和16位寄存器外,还提供了8个32位通用寄存器,所有这些寄存器在实模式下都可以被访问。此外,除8086、80286提供的4个段寄存器外,还增加2个附加数据段寄存器FS和GS,在实方式下也都可以使用。只有指令指针寄存器EIP和标志寄存器EFLAGS在实方式下的低16位可用。在实方式下,段的大小最大为64KB,EIP的高16位应为0。 5.7.2通用寄存器可作为指针寄存器在第3章的3.4.1小节中,已经说明386及其后继机型除提供16位寻址外,还提供了32位寻址。在实模式下,这两种寻址方式可同时使用。在使用32位寻址时,32位通用寄存器可以作为基址或变址寄存器使用。也就是说,允许32位通用寄存器作指针寄存器用。在实模式下,段的大小被限制于64KB,这样段内的偏移地址范围应为0000~FFFFH,所以在把32位通用寄存器用做指针寄存器时,应该注意它们的高16位应为0。 提示:32位通用寄存器可用做指针寄存器,但16位通用寄存器中仍然只有BX、BP和SI,DI可用做指针寄存器。所以,下列指令是合法的:MOVEAX,[BX]MOVEAX,[EDX]MOVAX,WORDPTR[ECX) 而下列指令是非法的:MOVAX,[DX];DX不是指针MOVEAX,[CX];CX不是指针在386及其后继机型中,允许同一寄存器既用于基址寄存器,也用于变址寄存器。因此,下列指令也是合法的:MOVAX,[EBX][EBX] 5.7.3与比例因子有关的寻址方式例5.22用比例变址寻址方式编写一程序,要求把5个双字相加并保存其结果。下面给出了所编写的程序。从程序中可以清楚地看出,采用比例变址寻址方式可以直接把数组的元素下标存入变址寄存器中,而比例因子1、2、4和8正好对应于数组元素为字节、字、双字和4字的不同情况。因此,这类寻址方式为数组处理提供了极大的方便。 .386STACKSEGMENTSTACK'stack'DW200DUP(?)STACKENDSDATASEGMENTarrayDD234556H,0F983F5H,6754AE2HDD0C5231239H,0AF34ABC4HresultDQ?DATAENDSCODESEGMENT START:MOVAX,DATAMOVDS,AXMOVAX,STACKMOVSS,AXSUBEBX,EBX;EBX寄存器清零MOVEDX,EBX;EDX寄存器清零MOVEAX,EBX;EAX寄存器清零MOVCX,5;设置循环次数为5BACK:ADDEAX,array[EBX*4];做32位加法ADCEDX,0;保存进位到EDX INCEBXDECCXJNZBACKMOVDWORDPTRresult,EAX;存低32位MOVDWORDPTRresult+4,EDX;存高32位MOVAX,4C00H;返回DOSINT21HCODEENDSENDSTART

当前文档最多预览五页,下载文档查看全文

此文档下载收益归作者所有

当前文档最多预览五页,下载文档查看全文
温馨提示:
1. 部分包含数学公式或PPT动画的文件,查看预览时可能会显示错乱或异常,文件下载后无此问题,请放心下载。
2. 本文档由用户上传,版权归属用户,天天文库负责整理代发布。如果您对本文档版权有争议请及时联系客服。
3. 下载前请仔细阅读文档内容,确认文档内容符合您的需求后进行下载,若出现内容与标题不符可向本站投诉处理。
4. 下载文档时可能由于网络波动等原因无法下载或下载错误,付费完成后未能成功下载的用户请联系客服处理。
大家都在看
近期热门
关闭