变量定义除分配存储空间和赋初值外,还可以创建变量名。这个变量名一经定义便具有以下两类属性。
● 地址属性,是指首个变量所在存储单元的逻辑地址,含有段基地址和偏移地址。
● 类型属性,是指变量定义的数据单位,有字节量、字量、双字量、3字量、4字量和10字节量,依次用类型名BYTE、WORD、DWORD、FWORD、QWORD和TBYTE表示。
在汇编语言程序设计中,经常会用到变量名的属性,因此,汇编程序提供有关的操作符,以方便获取这些属性值,如表2-7所示。
表2-7 常用的地址和类型操作符
地址操作符用于获取变量名的地址属性,主要有SEG和OFFSET,分别取得变量名的段地址和偏移地址两个属性值。中括号和美元符号与地址有关,也可以归类为地址操作符。
[例2-6]变量地址属性程序
列表文件总是将段开始的偏移地址假设为0(但并不表示主存中其偏移地址一定是0),然后计算其他数据或代码的相对偏移地址。头一个字节变量BVAR有两个数据,占用00000000H和00000001H存储单元。
变量名具有逻辑地址。数据段中直接使用变量名代表它的偏移地址(也可以加操作符OFFSET以示明确)。操作符“$”代表当前偏移地址值,即前一个存储单元分配后当前可以分配的存储单元的偏移地址。变量ARRAY的偏移地址为02H,再分配11个字变量数据后,当前相对偏移地址成为0018 H。所以,符号常量ARR_ SIZE =0016 H(=0018 H -0002 H),也就是ARRAY和WVAR变量所占存储空间的字节数:16 H=22(=[10+1 ]×2)。因为每个字变量值占两个字节,所以ARR_ LEN等于它们的数据项数(个数):ECX=000 BH=22 ÷ 2=11。
在程序代码中,用地址操作符“[]”(中括号)括起变量名指向其首个数据,通过带地址操作符的变量名加减常量存取以首个数据为基地址的前后数据。也就是说,[BVAR]表示它的头一个数据,故AL=12H;[BVAR+1]表示下一个字节的数据,故AH=34H。变量名后用“+n”或“[n]”,两者作用相同,都表示后移 n 个字节存储单元。所以,WVAR[2]是指WVAR两个字节之后的数据,即DVAR的前一个字量数据,故BX=DEF0H。
在代码段中,MASM还可以直接引用变量名(及加减常量),表示指向变量值,类似高级语言中常见的变量形式,但本质上是带有地址操作符括起存储器地址。本书中均使用地址操作符括起变量名,以便更加明确地表示访问变量值,避免误解。
代码段中通过“$”获得当前指令“MOV EDX,$”的偏移地址传送给EDX。
语句“MOV ESI,OFFSET DVAR”通过OFFSET操作符获得双字变量DVAR的偏移地址传送给ESI。“[ESI]”则指示该偏移地址的存储单元,从中获取一个双字数据,即DVAR变量值;而指令“MOV EBP,DVAR”也将使EBP等于DVAR变量值。
注意,程序的数据段和代码段开始的实际偏移地址不一定是0。所以,本例中ESI和EDX并不一定是列表文件的相对偏移地址18H和17H。
代码段最后调用本书配套子程序库中显示32位通用寄存器内容的子程序DISPRD,显示传送结果,如图2-8所示。大家也可以在任何位置调用该子程序,显示程序执行到该位置时通用寄存器的内容,以便与自己分析的结果进行对比。
图2-8 例2-6程序的运行结果
类型操作符使用变量名的类型属性。与大多数程序设计语言一样,在汇编语言中变量也需要先定义,并给定一种类型,每个变量通常表示相应类型的数值。类型转换操作符PTR用于更改变量名的类型,以满足指令对操作数的类型要求。“类型名”可以是BYTE、WORD、DWORD、FWORD、QWORD和TBYTE(依次表示字节、字、双字、3字、4字和10字节),还可以是由结构、记录等定义的类型。
MASM中,各种变量类型用一个双字量数值(在16位平台则是字量数值)表达,这就是TYPE操作符取得的数值。对变量,TYPE返回该类型变量的一个数据项所占的字节数,例如,对字节、字和双字变量依次返回1、2和4。TYPE后跟常量和寄存器名,则分别返回0和该寄存器能保存数据的字节数,这是因为常量没有类型,寄存器具有类型(8位寄存器是字节类型、16位寄存器是字类型、32位寄存器则是双字类型,依次返回1、2和4)。
对变量,还可以用LENGTHOF操作符获知某变量名指向多少个数据项,用SIZEOF操作符获知它共占用多少字节空间,即SIZEOF值=TYPE值×LENGHOF值。对于字节变量和ASCII字符串变量,LENGTHOF和SIZEOF的结果相同。
[例2-7]变量类型属性程序
本例采用与上例同样的数据段,这里只列出了代码段。
在指令“MOV EAX,DWORD PTR[ARRAY]”中,EAX是32位寄存器,属于双字量类型,变量ARRAY被定义为字量,两者类型不同,而MOV指令不允许传送不同类型的数据,所以利用PTR改变ARRAY的类型,结果是将ARRAY前两个字数据按照小端方式组合成双字量数据传送给EAX(=00020001H)。随后的指令利用类型操作符获取相关数值,EBX到EBP寄存器的内容依次是01H、02H、04H、0AH、14H和16H,如图2-9所示。
图2-9 例2-7程序的运行结果
除变量名外,还有段名、子程序名等伪指令的名字以及硬指令的标号,它们也都有地址和类型属性,也都可以使用地址操作符和类型操作符。我们将在后续章节学习它们。
注意,在前面的示例程序中,为了说明问题,我们既使用了32位数据、寄存器,也使用了8位和16位数据、寄存器。但对于32位指令集结构的IA-32处理器,一般的编程原则是:尽量使用32位操作数和寄存器,除非需要单独对8位(如ASCII码字符、字符串)或16位数据(从列表部分可以看出,机器代码前面有一个66H的指令前缀)进行处理。
【 NASM 】
在NASM汇编程序中进行变量定义与MASM类似,但需要注意以下几个区别。
● NASM同样使用DB、DW、DD等定义变量,但不支持BYTE、WORD、DWORD等变量定义助记符。
● NASM早期版本不支持变量定义的无初值符号“?”和复制操作符“DUP”,而是使用伪指令RESB、RESW和RESD分配未初始化存储空间,使用重复前缀TIMES起到MASM的DUP功能。但是,NASM从版本2.15开始兼容复制操作符DUP功能,在无初值数据段中的变量定义可以使用无初值符号“?”,不过不应在有初值数据段中使用符号“?”定义无初值变量。
● 在NASM汇编程序中,不论是数据段还是代码段,直接使用变量名表示其地址。但在代码段,MASM需要使用OFFSET操作符表示获得地址(新版本NASM也兼容),MASM直接使用变量名或者加中括号表示其变量值。NASM则必须使用中括号才表示获取变量值。
● 在WIN32等输出格式下,NASM不支持定位伪指令“ORG”。
● NASM同样使用BYTE、WORD、DWORD等类型符,但早期版本不使用PTR符号。新版本NASM识别PTR,表示对存储器访问,作用类似为变量名加上中括号。不过,不要在常量前使用PTR,直接用类型符就已经表示类型了。
● NASM定义的变量有地址属性,但并不记录变量的类型属性,因此也不支持MASM的TYPE、LENGTHOF、SIZEOF操作符。
了解上述区别后,可以将例2-2到例2-6的数据定义和程序代码应用于NASM汇编程序,并上机实践。注意将无初值符号“?”用0替代,注释掉ORG语句。如果采用早期NASM版本或不使用MASM兼容性,则应删除OFFSET和PTR操作符。