变量定义是为了给变量申请固定长度的存储空间,还可以将相应的存储单元初始化。
变量定义伪指令是最常使用的汇编语言说明性语句,它的汇编语言格式为:
变量名即汇编语句名字部分,是用户自定义的标识符,表示初值表首个数据的逻辑地址。汇编语言使用这个符号表示地址,故有时被称为符号地址。变量名可以省略,在这种情况下,汇编程序将直接为初值表分配空间,没有符号地址。设置变量名是为了方便存取它指示的存储单元。
初值表是用逗号分隔的参数,由各种形式的常量以及特殊的符号“?”和DUP组成。其中,“?”表示初值不确定,即未赋初值。而实际上,它在数据段中被汇编程序填充为0,完全可以用0替代“?”;只有在无初始数据段(MASM使用段定义伪指令“.DATA?”进行定义,常被称为.BSS段)才是程序执行时分配空间,真正的无初值。如果多个存储单元初值相同,可以用复制操作符DUP进行说明。DUP的格式为:
变量定义伪指令有DB、DW、DD、DF、DQ、DT(新版本也可以依次使用BYTE、WORD、DWORD、FWORD、QWORD和TBYTE),它们根据申请的主存空间单位分类,如表2-6所示。除了DB、DW、DD等定义的简单变量外,汇编语言还支持复杂的数据变量,如结构(Structure)、记录(Record)、联合(Union)等。
表2-6 变量定义伪指令
用DB定义的变量是8位字节量(Byte-sized)数据(对应C、C++语言中的char类型)。它可以表示无符号整数0~255、补码表示的有符号整数-128~+127、一个字符(ASCII码值),还可以表示压缩BCD码0~99、非压缩BCD码0~9等。
[例2-2]字节变量程序
在数据表达的例2-1中已经应用到不少字节定义变量,可以再次观察、深入理解。本示例程序重点说明无初值和重复初值的情况。
变量BVAR3无初值,表示在主存中为该变量保留相应的存储空间。既然有存储空间,就一定有内容,但内容应是任意、不定的,而事实上汇编程序中是用0填充的(像高级语言的编译程序一样)。所以,完全可以填0。
本示例程序通过DUP操作符为BVAR4定义了5个相同的数据,在左侧列表文件中用中括号表示。DUP操作符可以嵌套,最后一个无变量名的变量初值依次是:02 03 04 04 02 03 04 04。
用DW定义的变量是16位字量(Word-sized)数据(对应C、C++语言中的short类型)。字量数据包含高低两个字节,可以表示更大的数据。实地址方式下的段地址和偏移地址都是16位的,可以用16位变量保存。
[例2-3]字变量程序
每个DW伪指令定义的变量数据都是16位的,所以真值1和-1用字节量表达分别是01H和FFH,用字量表达则分别是0001H和FFFFH。对于有符号数据,最高位仍是符号位,负数真值-38H对应补码FFC8H(=[1]0000H-0038H)。
16位为两个字节,在以字节为基本存储单元的处理器主存中要占用两个连续的存储单元。例如,同样是无初值定义变量,前一个示例中的BVAR3占一个字节,本示例中的WVAR3占两个字节。那么高低两个字节是怎样存放在主存的两个存储单元的呢?是低字节数据存放在低地址存储单元、高字节数据存放在高地址存储单元,还是低字节数据存放在高地址存储单元、高字节数据存放在低地址存储单元?IA-32处理器采用前者,即“低对低、高对高”,称为小端方式(Little Endian);有些处理器采用后者,称为大端方式(Big Endian)。列表文件左侧将字变量WVAR6的两个初值以字量数据显示,按习惯高位在前、低位在后,分别是3139和3832。而字节变量BVAR6仍以字节量形式显示,所以两者存放的内容相同,如图2-6所示。这也可以从本程序执行后的屏幕显示结果(91289128)看出。
图2-6 数据的存放顺序
用DD定义的变量是32位双字量(Doubleword-sized)数据(对应C、C++语言中的long类型),占用4个连续的字节空间,采用小端方式存放。在32位平展存储模型中,32位变量可用于保存32位偏移地址、线性地址或段基地址。
[例2-4]双字变量程序
本示例程序定义的数据DVAR2似乎与前一个示例程序定义的WVAR2一样,但由于采用了双字类型,所以同样的数据占用4个字节。术语小端和大端来自《格列佛游记》(Gulliver's Travels)的小人国故事,小人们为吃鸡蛋从小端打开还是从大端打开发起了一场“战争”。专家在制定网络传输协议时借用了这个词汇,这就是计算机结构中的字节顺序问题,在多字节数据的传输、存储和处理中都存在这样的问题。就像吃鸡蛋无所谓小端还是大端一样,两种字节顺序形式各有特点,不能说哪一种顺序更好。只是有些情况更适合小端方式,而有些情况采用大端方式更快。例如,Intel公司采用小端方式,而大多数精简指令集计算机(RISC)则采用大端方式。
对于IA-32处理器采用“低对低、高对高”的小端方式,如果从偏移地址00405090H开始连续4个存储单元的内容依次是39H、31H、32H、38H,如图2-7所示,那么,存储地址00405090H处的字节量是39H,00405090H处的字量是3139H,00405090H处的双字量是38323139H。理解了这些,看到本示例程序运行后显示91289128就不奇怪了。
图2-7 小端存储方式
变量定义的存储空间是按照书写的先后顺序一个接着一个分配的。但是,定位伪指令可以控制其存放的偏移地址。
(1)ORG伪指令
ORG伪指令将参数表达的偏移地址作为当前偏移地址,格式是:
例如,从偏移地址100H处安排数据或程序,可以使用语句:
(2)ALIGN伪指令
对于以字节为存储单位的主存储器来说,多字节数据不仅存在按小端或大端方式存放的问题,还有是否对齐地址边界的问题。
对 N ( N =2,4,8,16,…)个字节的数据,如果起始于能够被 N 整除的存储器地址位置(也称为模 N 地址)存放,则对齐地址边界。例如,16位2字节数据起始于偶地址(模2地址,地址最低1位为0)、32位4字节数据起始于模4地址(地址最低2位为00)就是对齐地址边界。
难道不允许 N 字节数据起始于非模 N 地址吗?是,也不是。
有很多处理器要求数据的存放必须对齐地址边界,否则会发生非法操作。而IA-32处理器比较灵活,允许不对齐边界存放数据。不过,访问未对齐地址边界的数据,处理器需要更多的读写操作,其性能不如访问对齐地址边界的数据,尤其是有大量频繁的存储器数据操作时。
所以,为了获得更好的性能,常要进行地址边界对齐。例如,高级语言的编译程序往往会根据地址对齐原则优化代码。ALIGN伪指令便是用于此目的,其格式如下:
其中, N 是对齐的地址边界值,取2的乘方(2,4,8,16,…)。另外,EVEN伪指令用于实现对齐偶地址,与“ALIGN 2”语句的功能一样。
[例2-5]变量定位程序
通过列表文件可以看到,汇编程序将BVAR1安排在0100H相对地址,这是ORG伪指令指示的地址,否则第一个变量通常起始于0。
BVAR1之后的存储单元是0101H,但“ALIGN 2”语句指示对齐偶地址,所以WVAR2被安排在0102H处。
同样,“ALIGN 4”语句指示对齐模4地址,所以DVAR3占用0104H~0107H地址单元。下一个单元地址是0108H,已经对齐模4地址,所以分配给DVAR4,之前的“ALIGN 4”语句也就不需要调整存储地址。
指令代码也由汇编程序按照语句的书写顺序安排存储空间,定位伪指令也同样可以用于控制其偏移地址,但注意顺序执行的指令之间不能使用对齐伪指令ALIGN。