汇编第4章-常用数据处理技术.ppt
第四章,常用数据处理技术,所谓常用处理技术,是指常用指令、常用伪指令、变量定义和使用以及编程应用的技术。这种编程应用主要是针对一些数值型数据的运算和非数值型数据的处理,而这种处理常常是大量的、广泛的和带有技巧性的。,汇编语言程序的语句除指令语句外,还有伪指令语句。伪指令语句又称为说明语句或指示性语句。它不同于指令语句,它不是直接命令CPU去执行某一操作,而是命令汇编程序应当如何生成目标代码。例如控制汇编以实现数据定义、存储器分配、源程序开始和指示程序结束等功能。,伪指令无相应的目标代码,因此也称为伪操作。伪指令,特别是数据定义伪指令,在源程序设计中是必不可少的。本章主要介绍变量、伪指令和常用数据处理程序。最后通过大量案例说明其应用。,4.1 变量,4.2 常用伪指令,4.3 常用数据处理指令,4.4 字符串输入输出方法,习 题 4,1变量名变量的名字是它的外包装,是用来区分不同的存储区域的标识符号,是一种标识符。,4.1 变量,4.1.1 变量定义,不同的语言对标识符命名的规定有所不同,但大体上都把“以字母开头的字符数字串”作为基本规定,当然也可以用“_”或“”开头。8086的汇编语言还允许用下面的特殊符号作为标识符的构成符号:?、$、%和_。,标识符是一个或多个符号构成的符号串,汇编语言对标识符命名的完整规定是:(1)可用符号包括字母、数字和特殊符号“?”、“”、“$”、“%”、“_”。(2)不允许用数字作为第一个符号。,(3)名字的长度由131个字符组成,但一般不超过10个符号。(4)最少由一个符号构成,可以是字母、“_”或“”。(5)汇编语言不区分字母的大小写。,2.变量定义的方法变量的实质是存放数据的内存区域,所谓变量定义就是告诉汇编程序,在翻译时从某个地址起预留一定数量的内存空间,并在其中填上初值,还要建立变量与地址间的对应关系。所以变量定义是伪指令而不是指令。,【格式】变量定义的基本格式是:变量名 类型 初值表【说明】类型部分只能出现DB、DW、DD、DQ、DT这几种内部保留字,用以说明初值表中的每个数据占几个字节,对应关系如下:,DB 字节型,每个数据项占1字节;DW 字型,每个数据项占2字节;DD 双字型,每个数据项占4字节;DQ与DT很少用,不作说明。,初值表是用逗号分隔的若干个数据项,每个数据项的值是变量的一个初值项,占据“类型”规定的字节数,所以初值表一方面说明变量的初值是多少,另一方面也指明了变量占多少字节的存储空间。对于DW和DD类型,每个数据项的存储遵照“高字节在高地址,低字节在低地址”的原则。,(4)每个数据项的书写方法可以是任何数制的整数或者由整数构成的计算式,也可以是字符,如果用整数书写,可以是无符号数,也可以是带符号数。(5)当类型是DB时,初值表可以是任意长度的字符串,而DW类型只允许长度不超过 2的字符串。,(6)如果初值表需要填写若干个相同的值,可以用下面的形式表示把一个值重复若干次:重复次数 DUP(数据项)(7)初值表中可以用问号“?”作为初值项,含义是用户程序不设定初值,而由汇编程序安排,对此汇编程序将在翻译时把这类初值项都以数值0填充。,(8)任何段中都可以写变量定义,也允许把指令与变量定义写在一个段内,但通常是把程序所用到的所有变量集中在一个段内进行定义,而把指令写在另一个段中。习惯上把定义变量的段称为数据段,写指令的段称为代码段或指令段。,【例4-1】说明data段中定义的各变量的类型及初值情况,变量占据的内存字节数。data SEGMENT d1 DB 1 d2 DW 1234H d3 DD 12345678H d4 DB 1,2,3,d5 DB 123 d6 DB 30 DUP(35H)d7 DB 1,3 DUP(2),3,-3,1001B d8 DB 1,2,3,4,5,6 DB 7,8,9,10 d9 DW 12,AB d10 DW 3-5data ENDS,3.变量的三个基本属性任何变量表面上都以一个标识符的形式出现,也就是它的名字,每个变量都有三个数据与之相对应,这就是变量的三属性。变量的三属性:段属性、偏移量属性和类型属性,1)段属性 变量的段属性也就是变量所在段的段地址。变量定义必须写在一个段的范围之内,每个段在程序进入内存时都被操作系统安排一个确定的段地址。在编写程序时可以用段的名字指出某处要使用段地址,而这个段中的所有变量都统一地以这个段地址作为逻辑地址中的段部分。,如果在编写程序时需要使用某个变量的段地址,一种方法是用该变量所在段的段名。比如,对例4-1中定义的变量d1,如果要把它的段地址取到寄存器AX中,可以写作:MOV AX,data,取段地址的另一种方法是在变量名的前面加上保留字SEG。比如,下面的写法与上面指令的功能完全相同:MOV AX,SEG d1,保留字SEG是伪指令,用于告诉汇编程序,上述指令的源操作数是变量d1所在段的段地址,而不是变量d1中存放的数据。这两条指令中的源操作数都是立即寻址方式,操作数在指令当中。这与指令“MOV AX,d1”有着本质差异,后者的源操作数是直接寻址方式,操作数在内存当中,是“MOV AX,d1”的变形。,2)偏移量属性 变量的偏移量属性也就是变量所在段的段内偏移地址。在第2章中已经说明,偏移地址表示段内某一位置到段起始地址的距离,偏移地址为0表示就在段的起始处。,一个段中可以定义多个变量,每个变量占据一定的内存空间,到段起点的距离也就不一样,所以一个段内的各个变量都具有不同的偏移地址。在编写程序时,指令中使用某变量就是按照它的偏移地址到所在段中取出数据,或把数据存到相应内存。,如果在编写程序时需要使用变量的偏移地址,一种方法是在变量名的前面加上保留字OFFSET。比如,把例4-1中的变量d1的偏移地址取到寄存器BX中,写作:MOV BX,OFFSET d1,取偏移地址的另一种方法是用汇编语言中的一条专用指令。【指令格式】LEA D,S【功能】把源操作数S的偏移地址取到目的操作数D中。【说明】(1)这是一条数据传送类指令,不影响标志位。,(2)该指令专用于取源操作数的偏移地址,所以源操作数S一定是内存型寻址方式,可以是内存型操作数5种寻址方式中的任何一种。(3)当S是变量名形式的直接寻址方式时,变量名两边的方括号可以省略。(4)目的操作数D一定是寄存器型,且必须是16位的字型通用寄存器,不能是段寄存器。,LEA指令专门用于取偏移地址,而MOV指令中把变量名字的前面加上伪指令OFFSET作为源操作数,也可以取出偏移地址。这两种取偏移地址的方法在很多时候可以相互替代,但它们也有一些不同的地方,有必要把两者进行对比:,寻址方式不同。用OFFSET后接变量名的形式出现的操作数是立即寻址方式,LEA指令中的源操作数是内存型寻址方式。下面两条指令都可以把例4-1中的变量d1的偏移地址取到寄存器BX中,执行效果是一样的,可以相互代换。MOV BX,OFFSET d1 LEA BX,d1,LEA指令在功能上比OFFSET更强。通过例4-2中的几条语句的语法正误对比,可以准确地掌握两者的差别。【例4-2】设buf是一个变量,偏移地址是10H,BX1000H,SI200H,判断下列各语句的正确性,对正确的指令说明其功能。,(1)MOV AX,OFFSET buf(1)正确,常规用法,把buf的偏移地址10H作为立即数送到AX中。(2)MOV AX,OFFSET buf+3(2)正确,把“OFFSET buf”作为立即数,是10H,与另 一个立即数3相加,结果13H送到 AX中。,(3)MOV AX,OFFSET BX(3)错误,应该写作MOV AX,BX。(4)MOV AX,OFFSET BX+3(4)错误,应该先用MOV指令把BX的值送到AX中,再用ADD指令把AX的值加3。,(5)MOV AX,OFFSET bufBX(5)正确,把“OFFSET buf”作为立即数看待,是10H,源操作数是把BX的值加上立即数10H,得到1010H,再以1010H为偏移地址,与BX对应的缺省段寄存器DS一起构成逻辑地址,到内存中寻找操作数。该指令汇编的结果相当于MOV AX,BX+10H。,(6)MOV AX,OFFSET bufBXSI(6)正确,把“OFFSET buf”作为立即数看待,是10H,该指令相当于:MOV AX,BX+SI+10H。(7)LEA AX,buf(7)正确,常规用法,把buf的偏移地址10H送到AX中。,(8)LEA AX,buf(8)正确,与(7)的功能完全相同,是两种不同的写法。(9)LEA AX,buf+3(9)正确,计算出源操作数的偏移地址13H,送到AX中。,(10)LEA AX,BX+buf(10)正确,计算出源操作数的偏移地址1013H,送到AX中。(11)LEA AX,BX+3(11)正确,计算出源操作数的偏移地址1003H,送到AX中。,(12)LEA AX,BX+SI(12)正确,计算出源操作数的偏移地址1200H,送到AX中。(13)LEA AX,BX+SI+buf(13)正确,计算出源操作数的偏移地址1210H,送到AX中。,3)类型属性 类型属性也就是变量的类型,变量在定义时必须用DB、DW等伪指令说明类型。说明变量的类型,一方面告诉汇编程序在翻译时把该变量定义中的每个数据项用几个字节存放,另一方面说明该变量的使用方法。MOV等双操作数指令中的两个操作数必须是同一种类型。,汇编程序在翻译时要进行类型检查。当一个操作数是寄存器,另一个是变量时,两者的类型一致是正常情况,当两者类型不同时,汇编程序将以寄存器的类型为准进行翻译,并提出警告(Warning);当目的操作数是变量,源操作数是立即数时,就以变量定义时的类型为准。如果有必要,使用变量时可以临时改变类型,后面将以具体例子说明使用方法。,4.1.2 内存图内存图表示的内容有两个方面。一方面是存储器,通常由若干个叠放在一起的小方框表示,每个方框代表一个字节。虽然每个字节有自己确定的物理地址,但是,由于编写程序时使用的是逻辑地址,而逻辑地址到物理地址的转换由计算机自动实现,所以画变量分配的内存图时,一般标以各字节的偏移地址,段地址部分被省略。,在表示一个字节的小方框内填上数值,表示该字节中的内容。填写数值时可以用各种数制、各种写法,但用十六进制数会有助于理解。另一方面,内存图还要表示变量名与偏移地址的对应关系,把变量的名字写在对应的方框的边上。图4.1是一个内存图的实例。,图4-1 变量的内存图,图4.1总体上分为三部分,中间是表示内存各字节的方框,各方框的右边标以偏移地址,左边则标出变量的名字。从图4-1中无法看出3个变量的类型,这一点可以通过在变量名的下面标上类型加以弥补。,从另一个角度看,不标类型还说明变量是不注重类型的。换句话说,源程序翻译成机器语言之后,指令中没有变量而只有偏移地址,类型是通过寄存器的位数或者指令本身所带有的机器语言形式的BYTE或WORD类型指示加以区分的。,【例4-3】对于图4-1的各变量,确定下列指令中赋值操作的操作数是什么类型。(1)MOV AX,var1(2)MOV AL,var1(3)MOV WORD PTR var2,3(4)MOV BYTE PTR var2+1,3(5)MOV var2+3,3,【解】(1)字型,由寄存器AX是16位可知(2)字节型,由寄存器AL是8位可知(3)字型,由伪指令WORD PTR指示(4)字节型,由伪指令BYTE PTR指示(5)是变量var2定义时的类型,由图4-1不能分辨究竟是哪一种类型。,4.1.3 变量定义与内存分配的关系汇编程序在为变量安排内存时遵照下面的规则:(1)同一段内的变量具有相同的段地址。(2)按照段中变量定义的次序,依次对各变量分配偏移地址。(3)除非有其它伪指令说明,段内的第一个变量被分配在偏移地址为0处。,(4)一个变量占据内存的字节数由其类型和初值表中的项数决定。(5)除非有其它伪指令说明,一个变量分配完后,紧接着分配下一变量。【例4-4】说明下面的数据段中各变量的内存分配情况,画出相应的内存图。,data SEGMENT x1 DB 012 x2 DW ab x3 DB 2 DUP(32),13,10 x4 DD 1017H data ENDS,图4-2 例4-4的data段的内存分配情况,4.2 常用伪指令,1.OFFSET操作符 变量是数据的存放地,对变量最常见的操作是从变量中取出数据和把数据放入变量中,实现这些操作有两种方法:直接法和间接法。,4.2.1 OFFSET和SEG,直接法是在程序中直接使用变量的名字以表明对哪个变量进行操作。比如,设var是一个字型变量,则指令 MOV AX,var 是从变量中取出数据,送到AX中,而指令 MOV var,AX 完成反方向的数据传递。上述两条指令中,var都是直接指明使用的变量,是直接寻址方式。,而间接法是先用OFFSET伪指令,把变量的偏移地址取到某个地址寄存器中,如BX、SI、DI、BP等,然后再用寄存器间接寻址方式对原变量中的操作数进行操作。如:MOV SI,OFFSET var MOV AX,SI 或 MOV AL,SI这种间接法方便对变量数组进行操作。,2.SEG操作符 无论按直接法还是间接法使用变量,都会涉及变量的段地址问题,需要把段地址放在某个段寄存器中,一般是放在DS或ES中。取变量的段地址也有两种方法:一是用变量所在段的段名,比如在例4-1中,如果用指令 MOV AX,data就是取data段的段地址送到AX中。,另一种方法是用SEG伪指令放在某变量的前面,表示取该变量所在段的段地址,比如在例4-1中,变量d1是在data段中定义的,所以把上述指令换成MOV AX,SEG d1也是可以的。不论是用段名还是用“SEG 变量名”的形式,汇编程序都是把它作为常量处理。,4.2.2 ASSUME和PTR1.ASSUME伪指令ASSUME伪指令占一行,用于指出后续程序中所使用的变量、标号等标识符在涉及到逻辑地址的段地址部分时,用哪个段寄存器作为缺省段地址。【格式】ASSUME R1:S1,R2:S2,.,【说明】(1)格式中的Ri代表段寄存器名。必须是DS、ES、SS、CS四个之一,Si是段地址,只能是一个段名或者“SEG 变量名”的形式。(2)Ri:Si是一组对应关系,表示Si段中的标识符都使用Ri作为缺省段寄存器。,(3)ASSUME可以一次指定多个对应关系,其间用逗号分隔。这种写法实际上是多个ASSUME的简写形式,等效写法是:ASSUME R1:S1 ASSUME R2:S2,(4)在一个完整程序中,ASSUME伪指令在程序中最少出现一次,用于指明CS与哪一个段相对应。此时,CS对应的段必须是结束伪指令“END 标号”中标号所在段,从而确定程序的第一条指令在哪个段的哪个位置,称作程序的入口地址。,(5)可以用ASSUME伪指令指定两个或两个以上的段寄存器作为同一个段中标识符的缺省段寄存器。当数据定义与指令写在同一个段中时,就会出现以CS、DS甚至ES一起作为一个段的缺省段寄存器的情况。此时,有关数据的操作(取值、存数等)优先以DS作为段寄存器。,2.PTR操作符PTR是用于指定操作数类型的伪指令,它需要与类型保留字配合使用,其基本用法如下:类型 PTR 操作数,程序中有两种情况需要用到指定类型伪操作:一是操作数本身没有类型,需要明确该操作数的类型时使用这种用法,比如寄存器间接寻址方式的BX。另一种情况是已定义的变量本身有一定的类型,程序需要临时当作另一种类型来处理。当操作数是内存型时,其类型常有BYTE和WORD两种。,BYTE PTR 内存操作数;该内存操作数为字节类型.WORD PTR 内存操作数;该内存操作数为字类型.,另外,当PTR后的操作数是标号或过程名时,则类型包括两种:即NEAR类型和FAR类型。过程名即子程序名后面第5章将作详细讲解,在此不再作阐述。,4.2.3 ORG和1.ORG伪指令ORG伪指令单独占一行,基本格式是:ORG 地址表达式 功能:把地址表达式的值赋给后面的变量或标号作偏移地址。即接在ORG伪指令后面的内容就从该地址开始安排。,【例4-5】分析下面数据段定义中各变量的偏移地址。data SEGMENT v1 DB 5 ORG 6 v2 DW 6 ORG 2 v3 DB 8 v4 DW 9data ENDS,ORG伪指令可以改变段中各变量定义时原有的次序,按指定情况安排各变量的偏移地址。段定义中各变量的次序是v1、v2、v3、v4,而内存图上按地址由小到大依次是v1、v3、v4、v2。图4-3中的两个空格表示数据段中没有对此进行定义,但实际上汇编程序会填上0。,图4-3 例4-5中数据段的内存分配情况,2.$操作符$是汇编语言中的一个特殊符号,代表汇编程序在处理到$所在的位置时当前安排的偏移地址值。程序中出现的$可以作为常量看待,但是不同位置上的$,其代表的值是不同的。与一般的数据不同的是,通常所说的常量(数值)是没有类型的,但$所表示的数据一定是字型。$一般作为字型变量定义时的一个初值使用。,【例4-6】分析下面数据段中各$符所表示的值。data SEGMENT a DB$b DW$,$c DB$-a data ENDS,【解】变量a的定义中出现的$是表示ASCII码值为24H的符号;定义变量b时用的两个$没有加引号,第一个$代表0001H,第二个$代表0003H;在变量c的定义中,$出现在数值表达式中,两者相减的结果是5,并且不再有类型,因此可以作为字节型变量的一项初值。图4-4是该数据段对应的内存图。,图4-4 例4-6中数据段的内存分配情况,4.2.4 和 EQU,1、伪指令对于程序中经常使用的一个特定的数值,很多程序员喜欢把它定义成一个常量标识符,在书写后面的程序时若遇到该数值,就写相应的常量标识符而不写数值本身。这种做法的最大好处在于方便程序的修改,如果需要把一个程序中使用到的某一数据统一改为另一数据,在使用常量定义时就只要在定义处修改一次即可。,常量定义的基本格式是:标识符 数值表达式 常量定义可以写在源程序的任何地方,单独占一行,并且遵照“先定义后使用”的原则。【例4-7】分析下面程序段中标识符x每一次使用所表示的含义。,x=3MOV AL,xMOV BX,xx=5MOV BYTE PTR BX,xx=x+1ADD SI,x,【解】该程序段等价于下面的不用常量标识符的写法:MOV AL,3 MOV BX,3 MOV BYTE PTR BX,5 ADD SI,6,2EQU伪指令EQU称为符号定义伪指令,在使用方式上与常量定义的“”很相似,分为定义部分和使用部分。其定义格式是:标识符 EQU 符号串 格式中的符号串可以由分号“;”以外的任何符号构成,长度不限,但必须在一行内写完。,用EQU进行符号定义的语句可以写在源程序的任何位置。与常量定义的“”不同,对同一个标识符不允许用EQU进行两次定义。对EQU定义的标识符也无需要遵照“先定义后使用”的原则。,【例4-8】用不带符号定义的形式对下面的程序段进行改写。MOV AX,s1 s1 EQU WORD PTR BX s2 EQU BX+SI s3 EQU BX MOV BX,s3 MOV CX,s2 MOV DX,s25,【解】改写的结果如下:MOV AX,WORD PTR BX MOV BX,BX MOV CX,BX+SI MOV DX,BX+SI+5,4.2.5 INCLUDE伪指令【伪指令格式】INCLUDE 文件名【功能】告诉汇编程序,把“文件名”所指出的文本文件的内容调到INCLUDE伪指令所在处,对拼装后的源程序进行汇编。INCLUDE伪指令将在第5章子程序调用中使用,在此不再作阐述。,4.3.1 算术运算类指令,1、带进位的加法指令【指令格式】ADC D,S【功能】把目的操作数D、源操作数S以及进位标志CF的值相加,结果送回D中。,4.3 常用数据处理指令,【说明】这条指令与ADD指令相比,仅仅是参与加法运算的加数多了CF。ADC指令与ADD指令在对操作数的寻址方式要求上,以及运算结果对标志位的设置方法上都是一样的。该指令主要用于多字节整数相加。,【例4-9】设变量x和y中各放有一个双字型整数,存储方式符合“高字节在高地址,低字节在低地址”的规定,试编写程序段计算xy,结果放回x中。,【分析】这个问题的解决方法与手工计算两个十进制的两位数是一致的。比如计算3746,先用个位数的7与6相加,结果写本位的3并记下进位值1,然后用十位上3加4再加个位来的进位1,结果写8。这里个位相当于双字型整数的低字部分,十位相当于高字部分。低位的加法只有两个加数,当然用ADD指令,并把进位情况反映到CF上。高位相加时要考虑低位来的进位,当然用ADC指令进行高位加法。,【解】MOV AX,y ADD x,AX MOV AX,y+2 ADC x+2,AX,2.加1指令【指令格式】INC D【功能】把操作数D加1后的结果送回D中。【说明】(1)这是一条单操作数指令,要求操作数D必须是寄存器或内存型,且不能是段寄存器。如果是内存型,必须能够确定D的类型。D本身不能确定类型时,在D的前面加上BYTE PTR或WORD PTR指明类型。,(2)该指令对条件标志位的影响是:SF和ZF根据实际情况设置,对CF没有影响,对于OF,当执行前操作数是字节型的7FH或是字型的7FFFH时,执行后把OF置1,其余情况都把OF置0。INC指令可以用来把操作数的值加1,它与ADD D,1的差别主要在于对标志位CF的影响上。,【例4-10】设变量buf中存放了一个以回车符结束的字符串,编写程序段统计出串中有多少个空格,结果放在AX中。,【解】LEA BX,buf MOV AX,0 lab1:CMP BYTE PTR BX,13 JE lab3 CMP BYTE PTR BX,20H JNZ lab2 INC AX lab2:INC BX JMP lab1 Lab3:,3.带借位的减法指令【指令格式】SBB D,S【功能】用目的操作数D减去源操作数S,再减去CF的值,结果送到D中。除了多减一个CF的值外,SBB指令与SUB指令在对操作数的寻址方式以及对标志位的影响上是一样的。这条指令主要用于多字节型整数的减法。,4.减1指令【指令格式】DEC d【功能】把操作数d的值减1后,结果送回d中。对操作数寻址方式的要求与INC相同。DEC指令不影响CF;SF和ZF根据实际情况设置;对于OF,当执行前操作数是字节型的80H或字型的8000H时,执行结果OF置1,其余情况下OF置0。,5.求补操作【指令格式】NEG D【功能】对操作数D按位取反后再加1,结果送回D中。【说明】(1)这条指令的功能就是第1章中所述求补函数,它并不考虑操作数d是否带符号,只是按“取反加1”的规则处理。,(2)对操作数寻址方式的要求与INC、DEC指令的要求相同。(3)对标志位的影响:SF和ZF按实际情况设置;若执行前D0,则执行后CF置0,否则CF置1;若执行前D是字节型的80H或字型的8000H,则执行后OF置1,其余情况都将使OF置0。,6符号数乘法指令【指令格式】IMUL S【功能】带符号数的乘法指令。当S是字节型时,把ALS的结果放到AX中;当S是字型时,把AXS的结果放到DX与AX构成的双字中,DX放高16位,AX放低16位。不论操作数S中的数据来源如何,都当作带符号数。,7.符号数除法指令【指令格式】IDIV S【功能】带符号数除法指令。当S是字节型时,用AXS,商放在AL中,余数放在AH中;当S是字型时,用DX和AX组成的双字作被除数,除以S的商放在AX中,余数放在DX中。如果商超出数据的有效范围,将作为除法溢出处理,在屏幕上显示“Divide Overflow”字样,并结束程序的执行。,8数据符号扩展指令字节型符号扩展【指令格式】CBW【功能】对AL中的带符号数进行符号扩展,当AL0时,AH被赋值为0FFH,否则AH被置为0。该指令不影响所有标志位。,CBW指令用来把一个8位的带符号数转换成与其等值的16位带符号数,要求原数据放在AL中,而转换结果放在AX中,并且AX中的低8位部分(即AL)不变。这条指令的功能可以用下面的程序段实现:,CMP AL,0 JL lab1 MOV AH,0 JMP lab2 lab1:MOV AH,0FFH lab2:,CBW指令一般与IDIV指令配合使用。当程序中需要把一个字节型带符号数去除以另一个字节型带符号数时,按IDIV指令的要求,必须把字型的被除数放在AX中。为此就需要用CBW指令把放在AL中的字节型被除数进行符号扩展,变成字型,然后才能用IDIV指令进行除法操作。,2)字型符号扩展【指令格式】CWD【功能】对AX中的带符号数进行符号扩展,当AX0时,DX被赋值为0FFFFH,否则DX被置为0。该指令不影响所有标志位。,CWD指令用于把字型带符号数转换成双字型带符号数,结果放在DX和AX中。类似地也可以用一段程序实现该指令的功能。CWD指令一般与IDIV指令配合,用于被除数与除数都是字型带符号数的除法。,4.3.2 逻辑运算类指令,1交换指令【指令格式】XCHG D,S【功能】把操作数D与S的值交换。该指令不影响所有标志位。【说明】交换指令XCHG要求两个操作数中不能有立即寻址方式,不能有段寄存器,也不允许两个都是内存型寻址方式。,如果没有XCHG指令,交换两个数据需要3到4条指令。比如,XCHG AX,BX 就是下面3条指令构成的程序段的简化:MOV CX,AX MOV AX,BX MOV BX,CX,如果要交换两个字节型变量x和y中的内容,应用交换指令可以写成:MOV AL,x XCHG AL,y MOV x,AL,2逻辑与指令【指令格式】AND D,S【功能】把两个操作数按二进制形式,相应位进行逻辑与运算,结果放到操作数D中。,【说明】对操作数D和S的寻址方式的要求与MOV、ADD等典型双操作数指令的要求完全相同。运算结果将影响到标志寄存器的各条件标志位,具体设置情况是:CF 和OF总是被清0;运算结果的最高位复制到SF中;若运算结果各位都是0则ZF置1,否则ZF清0。,这是一条逻辑运算指令,但与高级语言中逻辑型数据的与运算不同,操作数D和S中并不是只放一个逻辑值,而是当作8个或16个逻辑值的组合。计算时,操作数相同位上对应的两个逻辑值相与,结果还放在这一位。任何一位的与运算对其它位不产生影响。,AND指令主要用于把目的操作数的指定位清0。具体做法是:在目的操作数中放需要清位的数据,源操作数一般以二进制形式写出,在目的操作数需要清0的对应位上写0,不清0而保留原值的对应位上写1。比如,把AL寄存器保留高4位不变,低4位清0,就可以用下面的指令实现:AND AL,11110000B,3.逻辑或指令【指令格式】OR D,S【功能】把两个操作数按二进制形式,相应位进行逻辑或运算,结果放到操作数D中。【说明】对操作数D和S的寻址方式的要求以及对标志位的影响与AND指令完全相同。,OR指令主要用于把目的操作数的指定位置1。具体做法是:在目的操作数中放需要置位的数据,源操作数一般以二进制形式写出,在目的操作数需要置1的对应位上写1,不置1而保留原值的对应位上写0。比如,把AL寄存器的最高位置1,其它位不变,就可以用下面的指令实现:OR AL,10000000B,4.逻辑非指令【指令格式】NOT D【功能】把操作数D按二进制形式,每位取反,结果放回操作数D中。【说明】对操作数D的寻址方式的要求与INC等典型单操作数指令一样,可以是寄存器寻址或内存型寻址方式,不能是段寄存器。操作数必须有确定的类型,可以使用段跨越。,5.逻辑异或指令【指令格式】XOR D,S【功能】把两个操作数按二进制形式,相应位进行逻辑异或运算,结果放到操作数D中。【说明】对操作数D和S的寻址方式的要求以及对标志位的影响与AND指令完全相同。,XOR指令主要用于对目的操作数的指定位取反,具体做法是:在目的操作数中放某些位需要取反的数据,源操作数一般以二进制形式写出,在目的操作数需要取反的对应位上写1,需要保留原值的对应位上写0。比如,如果AL寄存器中放了一个字母的ASCII值,下面的指令可以改变其大小写的情况:XOR AL,00100000B,如果XOR指令中的两个操作数相同,则结果一定是0,所以有时也用XOR指令把寄存器清0,如“XOR AX,AX”,与指令“MOV AX,0”相比,两者在执行效果上完全相同,但前者机器代码稍短,执行速度也稍快,并且能同时将CF清0。,【例4-11】已知AX的值为5BE9H,分别执行下列指令后,AX的值是多少?(1)ANDAL,AH(2)ORAH,AL(3)XORAH,AL(4)NOTAX,【解】根据各逻辑运算指令的功能,有:(1)AX的值为5B49H。(2)AX的值为0FBE9H。(3)AX的值为0B2E9H。(4)AX的值为0A416H。,6.位测试【指令格式】TEST D,S【功能】把两个操作数按二进制形式,相应位进行逻辑与运算,按结果设置条件标志位,结果不送回目的操作数。【说明】对操作数D和S的寻址方式的要求以及对标志位的影响与AND指令完全相同。,TEST指令与AND指令仅仅在“结果是否送回目的操作数”这一点上不同。TEST指令通常用于检测目的操作数的指定位上是0还是1。用法上与CMP指令有很多相似之处,都是设置标志位,然后用条件跳转指令完成分支操作。TEST指令总是与JZ或JNZ指令配合使用。,【例4-12】编写程序段,统计字型数组dataw中存放的100个整数中奇偶数各有多少个,结果DL中放奇数个数,DH中放偶数个数。【分析】尽管题目中并没有提及是无符号数还是带符号数,但不论哪种情况,奇数一定是二进制表示的最低位为1,偶数当然最低位是0。,【解】XOR BX,BX XOR DX,DX MOV CX,100 lab1:TEST dataw+BX,1 JZ lab2,INC DL JMP lab3 lab2:INC DH lab3:ADD BX,2 LOOP lab1,4.4 字符串输入输出方法,4.4.1 DOS的9号子功能字符串输出,【功能】屏幕字符串输出 具体操作:AH中放子功能号9;入口参数:DS:DX=待输出字符串首地址;INT 21H软中断调用;出口参数屏幕上显示出输出缓冲区的全部字符.,【说明】(1)被输出的字符串的长度不限,但必须连续存放在内存的某个地方,且以ASCII值为24H的字符$结束,中间可以含有回车符、换行符、响铃符等特殊功能符号,存放字符串的起始逻辑地址必须放在指定的寄存器DS和DX中。,(2)$符本身不输出到屏幕。(3)调用结果是把字符串中的各个字符从光标当前所在位置起,依次显示在屏幕上,直至遇到$为止,光标停在最后一个输出符号的后面。(4)如果程序中需要输出$,只能用2号子功能实现。,(5)9号子功能调用将影响AL的内容,不改变其余寄存器及标志寄存器的值。【例4-13】分析下面的程序,写出程序执行后的结果。,data SEGMENT buf1 DB Hello,13,10,this is an example.$,13,10 buf2 DB-END-$data ENDScode SEGMENT ASSUME CS:code,DS:dataMain:MOV AX,data MOV DS,AX,LEA DX,buf1 MOV AH,9 INT 21H MOV DX,OFFSET buf2 INT 21H MOV AX,4C00H INT 21H code ENDS END main,【解】执行结果如下:Hello,this is an example.-END【例4-14】对例4.13的程序稍做修改成为下面的情况,写出程序执行后的结果。,data SEGMENT buf1 DB Hello,13,10,this is an example.,13,10 buf2 DB-END-$data ENDS,code SEGMENT ASSUME CS:code,DS:datamain:MOV AX,data MOV DS,AX LEA DX,buf1 MOV AH,9INT 21H MOV AX,4C00H INT 21Hcode ENDS END main,【解】执行结果如下:Hello,this is an example.-END,4.4.2 DOS的10号子功能字符串输入,【功能】从键盘接收字符串存入输入缓冲区,并回显屏幕.,具体操作:1.AH中放子功能号10或0AH;2.入口参数:DS:DX=输入缓冲区的起始逻辑地址;3.INT 21H软中断调用;出口参数:输入缓冲区存入了键盘输入的字符串并回显屏幕.输入缓冲区有特定的要求,其内存图如图4-5所示。,图4-5 用DOS的10号子功能进行字符串输入 的缓冲区要求,【说明】(1)输入缓冲区是一段连续的内存区,首地址必须在调用10号子功能前放到指定的寄存器DS和DX中。(2)10号子功能在调用时等待操作员从键盘上按键,直到按下回车键为止,按键情况会显示在屏幕上,最后按下的回车键会导致回车操作。如果在按回车键之前发现输入有错误,可以使用退格键或向左的箭头进行修改。,(3)输入缓冲区的最前面一个字节(图4-5中len1处)的值由用户程序填写,用以指出输入缓冲区允许输入的最大字符数。该值是字节型无符号数,有效范围是0255,,最后按的回车键也计算在内。当已输入len11个字符后就只能按回车键了,按其它键都会被认为是不正确的输入而不被机器认可,并且喇叭还会发出“嘀”的一声响以示警告。如果len1=1,表示只能按1个键,这个键只能是回车键,按其它键都会有“嘀”的一声警告;如果len1=0,表示一个键都不能按,包括回车键在内的任何按键都会被拒绝并且发出“嘀”的警告声,但机器又在等待输入,这一矛盾将导致无限期等待,即死机。,(4)输入缓冲区的次字节(图4-5中len2处)是由DOS的10号子功能填写的。在调用前用户程序可把它设为任意值,用户程序填写的这个值对10号子功能调用没有任何影响。,(5)子功能调用完成后,输入的字符串以ASCII的形式从输入缓冲区的第3个字节起连续存放,最后一个字符是回车键(0DH)。第2个字节中放的是输入字符串的有效长度(最后的回车键不计算在内)。用户程序可以从缓冲区的第2字节起取得输入字符串的串长及各个字符。,【例4-15】设有数据段定义如下:d SEGMENT buf DB 10,11 DUP(0)d ENDS画出数据段的内存图,然后执行下面的程序段,设buf的缺省段寄存器为DS。,MOV AX,d MOV DS,AX MOV AH,10 LEA DX,buf INT 21H假设执行时键盘上的输入情况是在按B2C3后按回车键,画出程序段执行后的数据段的内存图。【解】见图4-6(a)和(b)。,a.程序段执行前的情况 b.程序段执行后的情况 图4-6 例4-15的程序段执行前后的数据段的内存图,a.程序段执行前的情况 b.程序段执行后的情况 图4-6 例4-15的程序段执行前后的数据段的内存图,4.4.3 字符数据处理程序设计案例,【案例4-1】编写完整程序,在屏幕上输出下面的图形:9 89 789 6789 56789 456789 3456789 23456789 123456789,【案例分析】该图形共有9行,可以用9次输出实现,每次输出一个字符串。每一行输出完后的回车换行操作,可以在定义字符串时直接在串尾加上回车符和换行符实现。该图形还有一个特点,就是每一行的后若干个字符与前一行的内容相同,只是后面比前一行多了一个字符。所以可以只定义一个字符串“123456789”,加上回车、换行符及结束符,第一次从“9”开始输出,第二次从“8”开始输出,最后一次输出整个字符串。,【案例求解】data SEGMENT buf DB 123456789,13,10,24Hdata ENDScode SEGMENT A