汇编语言-标识符和表达式

汇编语言

Posted by Kingtous on February 26, 2019

第4章-标识符和表达式

标识符和表达式是程序设计经常用到的两个基本概念。在用高级语言进行程序设计时,如果程序要对某个变化的量进行处理时,通常都要对该变化量定义一个具有某种数据类型的符号名,用该符号名也就等于使用了该变化量。在汇编语言中,也是如此,所不同的是它们的说明和引用方式不同。

4.1 标识符

在汇编语言中,标号、内存变量名、子程序名和宏名等都是标识符,它一般最多由31个字母、数字及规定的特殊字符(?、@、_、$)等组成,并且不能用数字开头。通常情况下,汇编语言不区分标识符中字母的大小写。

和高级语言的变量名一样,一般要求标识符尽可能取得有点含义,这会大大改善程序的可读性,并有助于对程序的理解。但标识符不能是汇编语言的保留字,汇编语言的保留字主要是指:指令助忆符、伪指令定义符、寄存器名以及一些具有特殊含义的字符串等。

例如:MSG1、ERRMSG2、ASC1、asc2等是合法的标识符,而1a、ah、mov等就不是合法的标识符。

试比较ABCDH和0ABCDH之间的差异。前者是标识符,而后者是十六位进制数值。

4.2 简单内存变量的定义

在编程序时,我们往往要根据程序的需要定义一些内存单元。在高级语言程序中,要给存储单元取一个符号名,然后通过引用该符号名来访问其所对应的存储单元,而在汇编语言程序中要灵活一些,它可以给存储单元取符号名,也可以不取符号名。当给存储单元取符号名时,则可通过该符号名来访问其对应的存储单元;当不给存储单元取符号名时,则可通过存储单元的偏移量(有效地址)来访问它。

汇编语言中,常见的数据类型有字节、字和双字等。下面介绍如何定义各种整型类型的内存变量。

4.2.1 内存变量定义的一般形式

定义数据变量语句是在程序中经常使用的伪指令语句,其一般格式如下:

[变量名]  数据定义符  表达式1[, 表达式2, …, 表达式n]  ;注释

  主要解释
  变量名必须是一个合法的标识符,它可以写,也可以不写;
  数据定义符用于确定内存单元的数据类型,常用的定义符有:DBDWDD等;
  表达式是定义内存单元时的初值表达式,一个定义语句可以有多个初值表达式,各表达式之间必须用逗号‘,’分开;如果某个存储单元没有初值表达式,则必须用一个问号‘?’来表示;
  在定义语句的后面可以书写注释内容,也可以不写。

在定义变量时,虽然可以不写变量名,但我们建议还是要写,因为不写变量名,就意味着只能用内存单元的偏移量来访问它。这时,一旦内存单元的偏移量发生变化,那么,程序中的所有引用都要修改,这不仅增加了程序维护的工作量,而且也容易因遗漏修改而出错。

4.2.2 字节变量

定义字节变量的定义符为DB/BYTE(Define Byte),每个字节只占一个字节单元。其中:BYTE是MASM 6.0及其以后版本的数据类型说明符,随后的其它类型说明符同此说明。

例如:

COUNTER DB 6
  DB ’A’, ‘D’, 0Dh, ‘$’
TABLE DB 1, 3, 5, 7, 9, 11

上面的定义语句经汇编后所产生出的内存单元分配情况如图4.1所示。图中的数据是用十六进制表示的(以后也如此,不再说明),由引号括起来的字符在内存中是存放其ASCII码值。所以,’D’和0Dh是不同的,前者是字符’D’,后者是数值12的十六进制编码。

06 41 44 0D 24 01 03 05 07 09 0B
                         

图4.1 内存单元的分配情况示意图

注意:在上例中,说明语句“DB ‘A’, ‘D’, 0Dh, ‘$’”之前并没有给出变量名,但我们可以从前面的变量名COUNTER一直往后数,或从TABLE往前数,来访问某存储单元,因为它们是一片连续的存储单元,这和高级语言的变量定义有点区别的。在高级语言中,我们一定要用某个标识符来说明变量,也必须用该变量名来访问其所对应的存储单元。

用定义符DB还可定义一种特殊的数据形式——字符串。在定义字符串时,必须用成对的单引号或双引号把所要的字符括起来,括号内字符的ASCII码将依次存放在相应的字节单元内。例如:

MSG1 DB ’I am a student.’

该说明语句所对应的存储单元分布如下所示。为了看起来方便,并没有用字符的ASCII码来存放在相应的存储单元内,而直接用该字符,请不要引起误解。

​ … ‘I’ ‘ ‘ ‘a’ ‘m’ ‘ ‘ ‘a’ ‘ ‘ ‘s’ ‘t’ ‘u’ ‘d’ ‘e’ ‘n’ ‘t’ …

上面的例子也可改写为另一种等价的语句:

MSG1 DB ’I’, ‘ ‘, ‘a’, ‘m’, ‘ ‘, ‘a’, ‘ ‘, ‘s’, ‘t’, ‘u’, ‘d’, ‘e’, ‘n’, ‘t’, ‘.’

显然,前者的说明要比后者方便得多,所以,在程序中都采用前者的书写方式。

4.2.3 字变量

定义字变量的定义符为DW/WORD(Define Word),每个字占用两个连续的字节单元。

例如:

Word1 DW 89H, 1909H, -1 DW 0abcdH, ?, 0

上述定义的内存分配如下所示。

​ … 89 00 09 19 FF FF CD AB – – 00 00 …

由于字变量的数据是按照“高高低低”的原则存于存储单元之中的,而字节数据是按照排列顺序存于存储单元中的,所以,它们的存储方式有所不同。

试比较下面两个定义的存储顺序,其中:41H和42H分别是’A’和’B’的ASCII码。

B1 DB ’AB’

W1 DW ’AB’

… 41h 42h 42h 41h …

4.2.4 双字变量

定义双字变量的定义符为DD/DWORD(Define Doubleword),每个双字变量占用二个连续的字单元(四个字节)。

DW1 DD 12345678H, ? DW2 DD 0abcd1243H

上述定义的内存分配如下所示。

​ … 78 56 34 12 – – – – 43 12 CD AB …

4.2.5 六字节变量

定义六字节变量的定义符为DF/FWORD(Define Farword)。顾名思义,每个六字节变量占用六个连续的字节。

DF1 DF 1234567890abH, -1 DF 1abcd23H

上述定义的内存分配如下所示。

abH 90H 78H 56H 34H 12H 0FFH 0FFH 0FFH 0FFH 0FFH 0FFH 23hH 0cdH 0abH 01H 00H 00H
                                       

4.2.6 八字节变量

定义八字节变量的定义符为DQ/QWORD(Define Quadword)。同理,每个八字节变量占用八个连续的字节。

DQ1 DQ 12345678H, 0H, -1234H DQ ?, 1238H, ?

第一个八字节常量12345678H在内存中的分配方式如下所示,其存储原则与前面相同。其它八字节常量的存储方式与此一致。

78 56 34 12 00 00 00 00
                   

4.2.7 十字节变量

定义十字节变量的定义符为DT/TBYTE(Define Tenbytes)。同理,每个十字节变量占用十个连续的字节。

DT1 DT 12345678H, 0H, -1234H DT2 DT ?, -1H

第一个十字节常量12345678H在内存中的分配方式如下所示,它同样按“高高低低”的原则来存储。其它十字节常量的存储方式与此一致。

78 56 34 12 00 00 00 00 00 00
                       

以上六个数据类型是汇编语言中最基本的数据类型,其中,前三个是在程序中经常使用的,后三个的使用频率不太高。

4.3 调整偏移量伪指令

调整偏移量伪指令是在内存变量定义时用来调整内存变量起始偏移量的,它们是在把源程序汇编成目标文件时起作用。常用的调整偏移量伪指令有:EVEN、ALIGNORG,它们的主要目的是:为了更有效地读取内存单元的内容。

4.3.1 偶对齐伪指令EVEN

偶对齐伪指令格式:

EVEN

伪指令的作用是:告诉汇编程序(Assember),本伪指令下面的内存变量从下一个偶地址单元开始分配。

如果下一个偏移量是偶地址,那么,该伪指令不起作用,否则,汇编程序将空出一个字节,从下一偶地址开始为其后变量分配内存单元。

假设有下列变量定义,并且变量B1的偏移量是偶数。

4.3.2 对齐伪指令ALIGN

对齐伪指令格式:

ALIGN Num

其中:Num必须是2的幂,如:2、4、8和16等。

伪指令的作用是:告诉汇编程序,本伪指令下面的内存变量必须从下一个能被Num整除的地址开始分配。

如果下一个地址正好能被Num整除,那么,该伪指令不起作用,否则,汇编程序将空出若干个字节,直到下一个地址能被Num整除为止。

4.3.3 调整偏移量伪指令ORG

调整偏移量伪指令格式:

ORG 数值表达式

伪指令的作用是:告诉汇编程序,本伪指令下面的内存变量从该“数值表达式”所指定的地址开始分配。

假设有下列变量定义,并且变量word1的偏移量为0。

word1 DW 1234h byte1  DB 56h word2  DW 0abcdh ORG 1 byte2  DB ? word3  DW ? byte3  DB ?

前三个变量定义的内存分布如图4.5的左边所示,但由于伪指令“ORG 1”的作用,说明其后面所说明的变量要从偏移量为“1”的内存单元开始存放。所以,后三个变量的内存分配如图4.5的右边所示。

这些变量的内存分配是相互重叠的,对某个变量的操作无疑会影响到与之重叠的变量。

另外,变量byte2、word3和byte3没有赋初值,如果赋初值的话,则重叠部分的内存单元的原来初值将被覆盖掉。

在以上三个伪指令EVEN、ALIGN和ORG中,伪指令EVEN的使用频率较高。

4.3.4 偏移量计数器的值

前面,我们介绍了几种改变偏移量计数器之值的方法,但在程序中还无法引用其值。汇编语言提供了一个特殊的符号“$”来引用偏移量计数器的值。

W1  DW $, $ ORG $+3   ;从当前地址开始空3个字节 B1   DB 43h 假设:在给变量W1分配内存单元时,当前偏移量计数器的值为2。

于是,变量W1后面第一个“$”代表数值2,第一个字分配后,此时偏移量计数器$的值就为4,所以,第二个“$”就代表数值4。

在分配完二个字之后,偏移量计数器的值变为6,$+3的值为9,所以,伪指令“ORG $+3”就表示下一个变量从偏移量为9的单元地址开始分配。

4.4.2 结构类型的定义

重复说明符DUP只能用于重复同一数据类型的变量说明,它不可以重复不同数据类型的变量说明。为了把一组不同类型的变量说明组合在一起,汇编语言提供了另一种复合数据类型说明符——结构类型说明符STRUC。

1、结构类型的定义

用STRUC和ENDS可以把一系列数据定义语句括起来作为一种新的、用户定义的结构类型。其一般说明格式如下:

结构名 STRUC [Alignment][, NONUNIQUE]

数据定义语句序列

结构名 ENDS

解释:结构名是一个合法的标识符,且具有唯一性。结构名代表整个结构类型,前后两个结构名必须一致。结构内被定义的变量为结构字段,变量名即为字段名。

一个结构中允许含有任意多个字段,各字段的类型和所占字节数也都可任意。如果字段有字段名,则字段名必须唯一。每个字段可独立存取。

、对齐方式(Alignment):可用1、2或4来指定结构中字段的字节边界(Byte boundary),其缺省值为1。见4.3.2节中的有关叙述;

、NONUNIQUE:要求结构中的字段必须用全名才能访问,见本小节中的“结构类型字段的引用”。

2、结构类型变量的定义

在定义某个结构类型后,程序员就可以说明该结构类型的内存变量。它的说明形式与前面介绍的简单数据类型的变量说明基本上一致。其定义格式如下:

[变量名] 结构名 <[字段值表]>

解释:1)、 变量名即为该结构类型的变量名,它可省缺。如果省缺,则不能用符号名来访问该内存单元; 2)、 字段值表是给字段赋初值,中间用逗号’,’分开,其字段值的排列顺序及类型应与该结构说明时各字段相一致; 3)、 如果结构变量中某字段用其说明时的缺省值,那么,可用逗号来表示;如果所有字段都如此,则可省去字段值表,但必须保留一对尖括号”<”、”>”。 例如:

COURSE1  COURSE <> ;使用缺省的初值 COURSE2  COURSE <1, ‘Pascal’, 60> COURSE3  COURSE <2, , 84> ;使用缺省的课程名 PEASON1  PEASON <1000, ‘张 三’, 34>

3、结构类型字段的引用

定义了结构类型的变量后,若要访问其结构中的某个字段,则可采用如下形式:

结构变量名.字段名

该引用方式与高级语言的字段引用方式完全一致,我们还可用偏移量来访问其中的某个字段,但此方法不直观,变动性大,所以,一般情况下,不提倡使用此方法。

例如:

EXAM1 STRUC F1 DW ? F2 DB ? EVEN ;偶对齐 F3 DW ? EXAM1 ENDS E1 EXAM1 <1234H,’A’,8765H> ;定义结构EXAM1的一个变量E1

下面二种方法都可以把结构变量E1中字段的内容赋给寄存器AX,但如果在字段F3之前增加或减少了字段,那么,这些引用需要改变吗?

(1)、用字段名直接引用

MOV AX, E1.F3

(2)、用字段的偏移量间接引用

LEA SI, E1 MOV AX, [SI+4]     ;其中4是字段F3的偏移量

4.4.3 联合类型的定义

联合数据类型是一种特殊的数据类型。它可以实现:以一种数据类型存储数据,以另一种数据类型来读取数据。程序员可以根据不同的需要,以不同的数据类型来读取联合类型中的数据。也就是说,在一些情况下,以一种数据类型来读取联合类型中的数据,而在另一些情况下,又以另一种数据类型来读取其数据。

1、联合类型的说明

联合数据类型其说明格式如下:

[联合类型名] UNION [Alignment] [,NONUNIQUE]

数据定义语句序列

[联合类型名] ENDS

联合类型中的各字段相互覆盖,即同样的存储单元被多个不同的字段所对应,并且其每个字段的偏移量都为0。

联合类型所占的字节数是其所有字段所占字节数的最大值。

、对齐方式(Alignment):可用1、2或4来指定结构字节的边界,其缺省值为1。它还用可伪指令ALIGN或EVEN来重新定界,也可用命令行选项/Zp来定界;

、NONUNIQUE:要求联合类型中的字段必须用全名才能访问,引用联合类型字段的方法见下面的“联合类型字段的引用”。

例如:

DATATYPE UNION BB DB ? ;定义一个字节类型的字段 WW DW ? ;定义一个字类型的字段 DD DD ? ;定义一个双字类型的字段 DATATYPE ENDS 联合类型DATATYPE的字段分布如图4.8所示。

* 在联合类型的最外层定义中,在伪指令UNION和ENDS的前面一定要书写该联合类型名,而在其嵌套定义的内层,伪指令UNION和ENDS之前一定不能写联合类型名。*(Union中的union没有名字)

例如:

UNION1 UNION BB DB ? WW DW ? UNION ;联合类型的嵌套定义形式  W1 DW ?  B1 DB ?
ENDS UNION1 ENDS

2、联合类型变量的定义

联合数据类型的变量只能用第一个字段的数据类型来进行初始化。

例如:

U1 DATATYPE <’J’> ;定义一个联合变量,并初始化其值 U2 DATATYPE <1234H> ;初始化错误,只能用字节数据来初始化 U3 UNION1 <1>

3、联合类型字段的引用

定义了联合类型的变量后,就可根据需要,以不同的数据类型或字段名来存取该联合类型中的数据。引用其字段的具体形式如下:

联合类型变量名.字段名

例如:

MOV U1.WW, 1234H ;给联合类型变量赋字数据 MOV AL, U1.BB ;AL=34H MOV BX, U1.WW ;BX=1234H MOV U1.BB, ‘A’ ;U1的值1241H,41H是’A’的ASCII码

4.4.4 记录类型的定义

1、记录类型的说明

汇编语言的记录类型与高级语言的记录类型不同,它是为按二进制位存取数据提供方便的。记录类型的说明要用到另一个保留字RECORD,其说明格式如下:

记录名 RECORD 字段 [, 字段, ……]

其中“字段”代表:字段名:宽度[=初值表达式]

解释: 1、记录名代表该记录类型; 2、记录类型可以由多个字段组成,每个字段之间要用逗号’,’分开; 3、字段的属性包括字段名、宽度和初值; 4、字段的“宽度”表示该字段所占的二进制位数,它必须是一个常数,并且所有字段的宽度之和不能大于16;如果记录的总宽度大于8,则系统为该记录类型分配二个字节,否则,只分配一个字节; 记录的最后一个字段排在所分配空间的最低位,然后对记录中的字段依次“从右向左”分配二进制位,左边没有分完的二进制位补0;

5、初值表达式给出的是该字段的缺省值。如果初值超过了该字段的表示范围,那么,在汇编时将产生错误提示信息;如果某字段没有初值表达式,则其初值为0。

例如:

COLOR RECORD BLINK:1, BACK:3=0, INTENSE:1=1, FORE:3 FLOAT RECORD DSIGN:1, DATA:8, ESIGN:1, EXP:4 记录类型COLOR有四个字段:BLINK、BACK、INTENSE和FORE,它们的宽度分别为:1、3、1和3,所以,该记录类型共有8位二进制,系统分配给它一个字节。

记录类型COLOR的二进制位分布如右图4.9所示。

图4.9 记录类型COLOR的二进制位分配示意图

记录类型FLOAT用来模仿《计算机原理》中的浮点数表示法,它也有四个字段:

DSIGN (尾数的符号位); DATA (尾数); ESIGN (指数的符号位); EXP (指数)。

它们的总宽度是14,所以,系统要给它分配二个字节。