Fork me on GitHub

深入理解《深入理解计算机系统》(二)

汇编基础内容,数据的表示与寻址方式

数据格式

由于是由 16 位体系结构扩展成 32 位的,Intel 使用术语“字(word)”表示 16 位数据类型。因此,我们称 32 位为“双字(double words)”,称 64 位数为“四字(quad words)”。下表给出了 C 语言基本数据类型对应的 x86-64 表示。标准 int 值存储为双字(32 位)。指针(在此使用 char * 表示)存储为 8 字节的四字,64 位机器本来就预期如此。x86-64 中,数据类型 long 实现为 64 位,允许表示的值范围较大。x86-64 指令集同样包含完整的针对字节、字和双字的指令。

C 声明Intel 数据类型汇编代码后缀大小(字节)
char字节b1
shortw2
int双字l4
long四字q8
char *四字q4
float单精度s4
double双精度l8
表-1 C 语言类型在 x86-64 中的大小。在 64 位机器中,指针长 8 字节

浮点数有两种形式:单精度、双精度。历史上还有过 80 位的浮点格式全套运算。在 C 语言中可以使用 long double 来进行声明。不过实现的硬件不如单双精度运算的高效。

访问信息

一个 x86-64 的中央处理单元(CPU)包含一组 16 个存储 64 位值的通用目的寄存器。它们用来存储整数数据和指针。下图显示了这些寄存器:
寄存器

操作数指示符

大多数指令有一个或多个操作数(operand),指示出执行一个操作中要使用的源数据值,以及放置结果的目的位置。x86-64 支持多种操作数格式,如 表-2 所示。源数据值可以以常数形式给出,或是从寄存器或内存中读出。结果可以存放在集群其或内存中。因此各种不同的操作数可能性分为三种:

  1. 立即数(immediate):用来表示常数值。在 ATT 格式的汇编代码中,立即数的书写方式是‘\$’后跟一个用标准 C 表示法表示的整数。例如\$-577 或 \$0x1F。不同的指令允许的立即数范围不同,汇编器会自动选择最紧凑的方式进行数值编码。
  2. 寄存器(register):它表示某个寄存器的内容,16 个寄存器的低位 1 字节、2 字节、4 字节或 8 字节中的一个作为操作数,这些字节数分别对应 8 位、16 位、32 位或 64 位。在 表-2 中,我们用符号 $r_a$ 来表示任意寄存器 $a$,用引用 $R[r_a]$ 来表示它的值,这是将寄存器集合看作一个数组 $R$,用寄存器标识符作为索引。
  3. 内存引用:它根据计算出来的地址(常称作有效地址)访问某个内存位置。因为将内存看成一个很大的字节数组,我们用符号 $M_b[Addr]$ 表示对存储在内存中从 $Addr$ 开始的 $b$ 个字节值的引用。为了简便,我们通常省去下标 $b$,

如下表唆使,有多种不同的寻址方式,允许不同形式的内存引用。表中底部用语法 $I_{mm}(r_b,r_i,s)$ 表示的是最常用的形式。这样的引用有四个组成部分:

  • 立即数偏移 $I_{mm}$
  • 基址寄存器 $r_b$
  • 变址寄存器 $r_i$
  • 比例因子 $s$,$s\in\lbrace 1,2,4,8 \rbrace$

基址和变址寄存器必须是 64 位的寄存器。有效地址被计算为 $I_{mm}+R[r_b]+R[r_i]·s$。引用数组元素时,会用到这种通用形式。其他形式都是这种形式的特殊情况,只是省略了某些部分。正如我们所见,引用数组和结构元素时,比较复杂的寻址模式是很有用的。

类型格式操作数值名称
立即数$\$I_{mm}$$I_{mm}$立即数寻址
寄存器$r_a$$R[r_a]$寄存器寻址
存储器$I_{mm}$$M[I_{mm}]$绝对寻址
存储器$(r_a)$$M[R[r_a]]$间接寻址
存储器$I_{mm}(r_b)$$M[I_{mm}+R[r_b]]$(基址+偏移量)寻址
存储器$(r_b,r_i)$$M[R[r_b]+R[r_i]]$变址寻址
存储器$I_{mm}(r_b,r_i)$$M[I_{mm}+R[r_b]+R[r_i]]$变址寻址
存储器$(,r_i,s)$$M[R[r_i]·s]$比例变址寻址
存储器$I_{mm}(,r_i,s)$$M[I_{mm}+R[r_i]·s]$比例变址寻址
存储器$(r_b,r_i,s)$$M[R[r_b]+R[r_i]·s]$比例变址寻址
存储器$I_{mm}(r_b,r_i,s)$$M[I_{mm}+R[r_b]+R[r_i]·s]$比例变址寻址

表-2 操作数格式。操作数可以表示立即数(常数)值,寄存器值或是来自内存的值。比例因子 s 必须是1、2、4 或者 8
0%