本节书摘来自华章计算机《计算机系统:系统架构与操作系统的高度集成》一书中的第2章,第2.5节,作者:(美)拉姆阿堪德兰(Ramachandran, U.)(美)莱希(Leahy, W. D.)著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2.5 高级数据抽象
截至目前,我们已经讨论了高级语言中的简单变量,例如char、int和float。我们将这些变量称为标量。这些变量需要的存储空间都是先验的。编译器可选择将标量变量放在寄存器或者内存中。然而,对于高级语言通常支持的数据抽象,如数组和结构,编译器只能把它们分配在内存中,除此之外别无选择。回想一下,由于可寻址性的问题,处理器中寄存器的数量通常只有十来个。所以,这些数据结构庞大的体积排除了将它们分配在寄存器中的可能。
2.5.1 结构
高级语言中的结构数据类型可以通过基址加偏移量的寻址模式来提供支持。
考虑如下C语言结构:
如果这个结构的基地址在某个寄存器rb中,那么访问结构中的任意字段都可以通过提供一个相对于基址寄存器的偏移量来完成。编译器知道每个数据类型需要多少空间,也知道每个变量在内存中的对齐情况。
2.5.2 数组
考虑如下的声明:
这里的a所指的并非是单个变量,而是变量a[0],a[1]等组成的一个数组。由于这个原因,数组也常被称为向量。这种变量需求的空间在编译的时候可能知道也可能不知道,这取决于高级语言的语义。许多编程语言允许数组在运行时动态地决定大小而不是在编译的时候确定。这意味着,在编译期间编译器不知道数组所需要的存储空间。与之相反的是,标量在编译时是知道所需空间大小的。因此,编译器通常会使用内存来为这些向量变量分配空间。
编译器可能会将变量a在内存中按下图排布:
考虑下面这条操作数组的语句:
为了编译前面这条语句,假设指令集只允许ALU使用寄存器,那么首先我们需要将a[7]从内存中取出。显然这是可行的,使用我们已经介绍过的基址加偏移量寻址模式:
rb初始化为100时,上面这条指令就完成了将a[7]加载到r1中的工作。
一般来说,数组常在循环中使用。在这种情况下,可能有个循环计数器(设为j),它被用来索引数组。考虑下面的指令:
在上面的语句中,相对于基址寄存器的偏移量是不固定的。它由循环当前的索引值得到。尽管还可以生成代码来加载a[j],在能够计算出a[7]的有效地址之前还需要额外的指令。所以,一些计算机体系结构提供了一种寻址模式允许有效地址来自两个寄存器内容之和。这被称为基址加索引的寻址模式。
每条新指令和每种新寻址模式给实现增加了复杂性,因此需要非常小心地衡量其中的利弊。这通常由花费/性能分析来完成。例如,为了增加基址加索引寻址模式,我们需要问以下几个问题:
1)在程序的执行中,这种寻址模式有多常用?
2)从减少指令条数的角度来说,基址加索引寻址模式相对于基址加偏移量寻址模式有什么优势?
3)从执行时间的角度来说,使用基址加索引寻址模式的加载指令与使用基址加偏移量寻址模式相比需要付出什么代价?
4)为了支持基址加索引寻址模式,需要什么额外的硬件?
对上面四个问题的回答将给我们提供一个定量的标准来判断是否应该将基址加索引寻址模式包含进去。
我们在后面讨论处理器实现和性能影响时还会回头考虑如何评价向处理器中添加新指令和新寻址模式。