《汇编程序设计与计算机体系结构:软件工程师教程》 —3.3 定义数据
3.3 定义数据
汇编语言的数据类型根据数据的大小来确定(例如 8 位、16 位、32 位),而不像高级语言那样根据数据的内容来确定(例如整数、双精度浮点数、字符串)。无论数据是什么内容,你都应该从一套默认的数据类型中选出一种来定义这份数据。表 3-11 列出了各种汇编器所支持的数据类型。
表 3-11 以汇编器所支持的默认数据类型来定义数据时所需使用的命令
从这张表格来看,MASM 支持的数据类型似乎比 GAS 及 NASM 更多,但其实并不能这样比较,因为 MASM 在执行很多操作的时候都会根据上下文推测数据的类型,而 GAS 与 NASM则不会做出这样的假设。同一份数据在这三种汇编器里都是可以正常使用的,只不过后两者要求你必须明确说出指令所操作的是什么类型(或者多大)的数据。
编程知识:变量的大小一定要选对。开发者必须要考虑到变量的取值范围,从而采用尺寸合适的数据类型来定义该变量,以避免语义错误。这种错误属于逻辑上的错误,尽管它不会导致程序无法通过汇编或令程序在执行过程中崩溃,但产生的结果却是不正确的。比方说,如果你把用户输入的两个值保存到 8 位的变量中并对其相乘,那么就应该考虑到运算结果有可能超过 8 位,因此,要想正确地保存运算结果至少需要 16 个二进制位才行。如果保存运算结果的变量太小,就有可能令一部分数据丢失进而产生错误的结果。
变量采用下列语法来定义。
GAS/NASM
MASM
无论采用哪种汇编器,.data 段里定义的变量都必须予以初始化,也就是必须具备初
始值。
GAS
MASM
NASM
请注意,上述三个例子中的第二条语句都带有多个用逗号隔开的初始化器。这种一个标识符后面带有多个初始化器的写法可以用来创建数组(array),也就是创建一系列大小相同的值。这些值都可以通过数组的名称加以引用,该名称本身指的是序列中的第一个值(或序列中的第一个值在内存中的位置)。以 MASM 版本的代码为例,它并没有采用 4 个不同的标识符分别定义 4 个双字值,而是创建了名为 myArray 的数组,令其成为由 4 个双字值( 4 个 32 位值)构成的序列。充当数组名称的标识符其实是指向数组中首个值的引用,它表示的是该值在内存中的位置。第 4 章会讲解怎样访问数组中的值。
有的时候,程序中需要定义未初始化的变量。这种变量不具备初始值,它主要是为了在内存中预留一定的空间以供开发者使用。对于这种变量,不同的汇编器所采用的定义方式之间有很大的区别。GAS 与 NASM都要求 .data 段里的变量必须用明确的值来初始化,而 MASM则允许开发者在该段中采用问号(?)充当变量的初始化器来定义未初始化的变量。
MASM
MASM 的初始化器可以用 DUP 括起来,以便反复创建大小与内容均相同的多个值。凡是有效的初始化器都可以用 DUP 命令加以重复,这也包括问号。在刚才那段代码中,myArray 是由 10 个字节所构成的数组,其中每个字节的初始值都是 1。myUArray也是由 10 个字节所构成的数组,它的每一个字节都用 ? 加以初始化(或者说,它的每一个字节都不设定明确的初始值)。
GAS 与 NASM要求未初始化的变量必须创建在 .bss 段中(bss 的意思是 Block Started by Symbol,以符号开始的块),而且要用特定的命令来创建。表 3-12 列出了这些命令。
表 3-12 创建各种数据类型的未初始化变量时所需使用的命令
下面这两段代码演示了怎样用 GAS 与 NASM来定义未初始化的变量。请注意,GAS 定义这种变量所用的语法是 [directive identifier, reserved_bytes]([命令 标识符,预留的字节数]),这与定义带有明确初始值的变量时所用的 [identifier: directive initializer]([标识符:命令 初始化器])有所不同。这两种汇编器都能够通过相应的命令,以单个变量或数组的形式来预留各种数据类型的未初始化空间。例如,NASM 版本的代码就预留了 64 个字节的空间并将其命名为 buffer,这块空间可以当成包含 64 个字节型数值的数组来使用。
GAS
NASM
前面说过,字符串是以 BYTE(字节)数组的方式存储的。字符串必须以 null 结尾,也就是说,其最后一个字节必须是值为 0 的 ASCII 码。不同的汇编器采用不同的方式来定义这种字符串。GAS 是在字符串末尾写上 \0,表示该字符串以 null 结尾,而 MASM 与 NASM则是直接用字面量 0来设置最后一个字节。此外,GAS 还可以用 .ASCIZ 命令给字符串末尾自动添加一个值为 0 的字节。
一个涉及字符串的重要问题是换行。不同的汇编器采用不同的方式换行。GAS 用转义符 \n 来插入换行符号(line-feed character,简称 LF)。MASM 用十六进制码 0Dh 与 0Ah来表示 CR/LF 这两个符号并以此实现换行(CR 的意思是 carriage-return,回车)。NASM 则只用 0Ah 这一个十六进制码(也就是 LF)表示换行。
GAS
MASM
NASM
符号常量(symbolic constant)可以在 MASM 版本的汇编代码里取代某些变量,用以表示程序执行期间绝对不会变化的值。x86 的符号常量能够表示 32 位整数,x86_64 的符号常量能够表示 64 位整数,常量所表示的都是基于整数的数据。你可以用等号(=)定义这样的常量:
MASM
符号常量有一些优点。比方说,如果程序里需要多次用到某个值(例如雇员的时薪),那么专门用一个标识符来表示这个值要比每次用到的时候都写一遍更加合理,这是因为:(1) 可以给这个值起个有意义的名字,例如 HOURLY_WAGE,令代码更容易阅读;(2) 由于该值只需要在定义这个标识符的时候书写一次,因此,以后如果要加薪或减薪,也只需在这行代码中修改即可而不用把提到该值的每一个地方全都修改一遍。此外,MASM 里的符号常量还有个好处就是不占内存,因为 MASM 在对代码做汇编的时候,会把所有出现符号常量的地方都改成该常量所对应的实际值。
MASM
汇编的过程中上述代码会自动加以转换,所有出现符号常量的地方都会自动变成该常量所表示的字面量。
采用等号来定义符号常量的写法只适用于 MASM 汇编器。此外,MASM 的符号常量还可以用来表示字符串值。
所有的汇编器都可以把表达式的值表示成符号常量。符号必须用 EQU 命令来定义。与定义变量时的要求不同,用 EQU 所创建的符号既可以出现在数据段也可以出现在代码段(对于 GAS 汇编器,它可以出现在 .data 与 .text 段,对于 MASM 汇编器,它可以出现在 .data 与 .code 段,对于 NASM 汇编器,它可以出现在 SECTION .data 与 SECTION .text 段)。这些汇编器在对代码做汇编的时候会把每一个出现这种符号的地方都替换成对应的表达式。
GAS
MASM
NASM
从刚才这些规则可以看出,各种汇编器在定义符号常量时所采用的办法有很大的区别。笔者建议大家只针对数值表达式来使用 EQU 命令。
EQU 命令还有一个用途,是通过创建符号常量来表示某个标识符所指代的数据占用了多大的内存空间。下面给出的 GAS 与 NASM 范例代码,采用表达式来计算字符串所占据的字节数。当前位置计数器(current location counter)用来指代位置计数器的当前内存地址(GAS 用句点.表示,MASM 及 NASM 用美元符号$表示),将该地址与前一个字符串的起始内存地址相减即可算出字符串所占据的字节数。MASM 可以通过等号(=)把计算结果定义成标识符。
GAS
MASM
NASM
MASM 还提供了一种写法,能够创建动态符号,称为文本宏(text macro)。由 TEXTEQU 命令所创建的符号可以表示涉及其他符号的表达式,甚至还可以表示指令。
MASM
下面这段范例代码就演示了怎样用这种符号来表示指令,请注意,由于指令不是整数表达式,因此它的左右两端是用尖括号括起来的。
MASM
把 movFreq 这个文本宏创建好之后,你可以在任何一行代码中通过该符号来执行它所表示的那条指令,也就是 mov eax, freq。
MASM
- 点赞
- 收藏
- 关注作者
评论(0)