本章目标
2.4 寻址方式
现在我们已经看到了一些汇编语言程序的简单例子。一般来说,一个程序是对存储在计 算机存储器中的数据进行操作的,这些数据可以用多种方式组织,而这些方式能够反映出信 息的本质以及怎样使用信息。程序员使用数据结构(data structure)如链表和数组去组织用 36~
39
于计算的数据。
程序通常是用高级语言编写的,在高级语 言中程序员可以方便地描述将要在各种数据结 构上执行的操作。当把一个高级语言程序翻 译成汇编语言时,编译器生成适当的低级指令 序列来实现所需的操作。指明指令操作数位置 的不同方法称为寻址方式。在这一节 中, 我 们给出RISC 风格处理器中的基本寻址方式,
在 表2-1 中给出了一个简要的描述,同时表中 包含了每一种方式中我们将会用到的汇编语法。
汇编语法定义了指定指令及其操作数寻址方式 的方法,这将在2.5 节中讨论。
2.4.1 变量和常数的实现
变量几乎在任何一个计算机程序中都会存在。在汇编语言中,一个变量通过分配一个保 存该变量值的寄存器或是存储单元来表示。这个值可以根据需要使用适当的指令来改变。
在图2-5 的程序中只使用了两种寻址方式访问变量。我们通过指明寄存器的名称或是操作 数被装入的存储单元地址来访问操作数。这两种方式的具体定义是:
寄存器方式(register mode)—— 操作数是一个处理器寄存器中的内容,在指令中给出寄 存器的名称。
绝对方式(absolute mode)——操作数在一个存储单元中,指令中明确地给出了这个单元 的地址。
由于在RISC 风格的处理器中,一条指令必须放到一个单字中,所以能用来指定绝对地址 的位的数量是有限的,如果字长为32 位则这个数量通常就是 16 位。为了生成 32 位的地址,
通常通过将位 b15的值复制到位位置 b31-16中(符号扩展)来将这个16 位的值扩展为 32 位。
这意味着可以用这种方式指定一个绝对地址,但只能得到一个有限范围的完整地址空间。我们 将在2.9 节中讨论关于如何指定完整的 32 位地址的问题。为了保持例子的简洁,现在我们假 设程序中所有存储单元的地址都以16 位来指定。
指令
Add R4, R2, R3
的三个操作数都使用了寄存器寻址方式。寄存器R2 和 R3 保存着两个源操作数,而 R4 则为目 的寄存器。
绝对寻址方式可以在程序中表示全局变量。例如在高级语言程序中有这样一个声明:
Integer NUM1, NUM2, SUM;
它将导致编译器为变量NUM1、NUM2 和 SUM 分别分配一个存储单元。每当它们在以后的程 序中被引用时,编译器就产生使用绝对方式去访问这些变量的汇编语言指令。
绝对寻址方式用于指令
Load R2, NUM1 该指令把存储单元NUM1 中的值加载到寄存器 R2 中。
表示数据或地址的常数几乎在任何一个计算机程序中都会存在。这样的常数在汇编语言 中可以用立即寻址方式来表示。
40
41 表2-1 RISC 风格的寻址方式
名称 汇编语法 寻址功能
立即方式 值 操作数= 值
寄存器方式 Ri EA=Ri
绝对方式 LOC EA=LOC
寄存器间接方式 (Ri) EA=[Ri]
变址方式 X(Ri) EA=[Ri]+X 带变址的基址方式 (Ri, Rj) EA=[Ri]+[Rj]
EA= 有效地址 Value= 有符号数 X= 变址值
立即方式(immediate mode)——操作数在指令中被明确地给出。
例如指令
Add R4, R6, 200immediate
表示将数值200 与寄存器 R6 的内容相加,并把结果存入寄存器 R4 中。在汇编语言中使用下 标来说明立即方式是不合适的。常用的约定是在这个值的前面使用符号“#”来说明这个值是 作为立即操作数使用的。因此,我们用以下方式写出上面那条指令:
Add R4, R6, #200
在下面的寻址方式中,指令没有明确地给出操作数或者其地址。取而代之的是,指令中 提供了一些信息,当指令被执行的时候,处理器可以从这些信息中推导出有效地址(effective address,EA),然后再利用有效地址去访问操作数。
2.4.2 间接和指针
图2-6 中的程序需要在每次循环时能够修改存储器操作数的地址。提供这一功能的一个好 方法是用一个处理器寄存器保存操作数的地址。在每次遍历时,改变(递增)这个寄存器的内 容,以提供列表中下一个将要被访问的数的地址。这个寄存器担当着列表指针(pointer)的角 色,我们称列表中的每个数据项是通过使用寄存器中的地址间接(indirectly)访问的,所需的 功能是由间接寻址方式提供的。
间接方式(indirect mode)——操作数的有效地 址是一个寄存器中的内容,而这个寄存器在指令中 给出。
我们通过在指令中使用圆括号的方法来表示间 接寻址,在圆括号中放置寄存器的名称,如图2-7 和表2-1 中给出的示例那样。
为了执行图2-7 中的 Load 指令,处理器将寄存 器R5 中的值作为操作数的有效地址。它请求一个读
操作从存储器中取出单元B 的内容。这个从存储器中读出的值是所需要的操作数,处理器将 它加载到寄存器R2 中。间接寻址也可以通过一个存储单元完成,但只有在 CISC 风格的处理 器中才会出现。
间接方式和指针的使用在程序设计中是一个重要且强大的概念。它们允许使用相同的代 码来对不同的数据进行操作。例如,图2-7 中的寄存器 R5 作为 Load 指令的一个指针,用来 把存储器中的操作数加载到寄存器R2 中。有一段时间 R5 可能指向存储器中的单元 B。然后,
程序可能改变R5 的内容使其指向不同的单元,此时相同的 Load 指令将会把那个单元中的值 加载到R2 中。因此,只需要改变指针的值,就可以方便地重用包含该 Load 指令的程序段。
现在我们回到图2-6 对一个列表中的数进行加法操作的程序中。间接寻址可以用来访问列 表中的连续的数,程序的修改结果在图2-8 中给出。寄存器 R4 被当作指向列表中的数的指针,
操作数通过R4 被间接地访问。程序的初始化部分从存储单元 N 中将计数器的值 n 加载到 R2 中。然后使用Clear 指令将 R3 的内容清为 0。下一条指令使用立即寻址方式将地址值 NUM1 放置到R4 中,NUM1 是列表中第一个数的地址。需要注意的是,我们不能使用 Load 指令去 加载所需要的立即值,因为Load 指令只能对存储器源操作数进行操作。取而代之,我们使用 Move 指令
Move R4, #NUM1 42
43
R5 Load R2, (R5)
B
B
主存储器
操作数
图2-7 寄存器间接寻址
Load R2, N Clear R3
Move R4, #NUM1 LOOP: Load R5, (R4)
Add R3, R3, R5 Add R4, R4, #4 Subtract R2, R2, #1 Branch_if_[R2]>0 LOOP Store R3, SUM
加载列表的大小 将和初始化为0 获取第一个数的地址 获取下一个数 把这个数加到和中 递增指向列表的指针 递减计数器
如果没完成就跳回到前面 存储最终的和
图2-8 在图 2-6 的程序中使用间接寻址
在很多RISC 风格的处理器中,有一个通用寄存器专用于保存常量值 0。通常,这个寄存 器是R0。它的内容不能通过程序指令改变。在我们关于 RISC 风格处理器的讨论中,假设 R0 就是以这种方式使用的。然后上面的Move 指令可以这样实现:
Add R4, R0, #NUM1
通常情况下,为了程序员方便,Move 作为一条伪指令(pseudoinstruction)使用,但是实际上 是由Add 指令实现的。
图2-8 中循环体里的前三条指令实现了图 2-6 中从 LOOP 开始的未说明的指令块。第一次 循环时,指令
Load R5, (R4)
从单元NUM1 中取出操作数并将其装入 R5 中。第一条 Add 指令把这个数加到寄存器 R3 的 总和中。第二条Add 指令将指针 R4 的内容加上 4,这样在第二次通过循环执行 Load 指令时,
R4 中将包含着地址值 NUM2。
作为另一个指针的例子,再来看C 语言语句 A = * B;
这里B 是一个指针变量,符号“*”是间接访问的操作符。这个语句使得 B 所指向的存储单元 的内容被装入存储单元A 中。这个语句可以被编译成
Load R2,B Load R3,(R2)
Store R3,A
通过寄存器进行间接寻址已经被广泛应用。图2-8 中的程序展示了它所提供的灵活性。
2.4.3 变址和数组
下一个我们将要讨论的寻址方式提供了访问操作数不同方式的灵活性。它对于列表和数 组的处理很有用。
变址方式(index mode)——操作数的有效地址是通过将一个寄存器中的内容加上一个常数 值而生成的。
为了方便起见,我们将这种方式中使用的寄存器称为变址寄存器(index register)。通常,
变址寄存器就是一个通用寄存器。我们表示变址方式的符号是 X (Ri)
这里X 表示在指令中包含的有符号整数的常数值,而 Ri 是相关寄存器的名字。该操作数的有 效地址用下面的式子给出:
EA = X + [ Ri ]
44
寄存器的内容在生成有效地址的过程中不能被改变。
在汇编语言程序中,每当需要一个常数如X 时,都可以直接给出或用一个表示数值的符 号名给出。在这种方法中,一个符号名是与一个特定的数值相关的,这将在2.5 节中讨论。当 指令被转换成机器码时,常数X 作为指令的一部分被给出并且被限制为比计算机的字长要少 的位数。因为X 是一个有符号的整数,所以在将它与寄存器中的内容相加之前,必须对它进 行符号扩展使其与寄存器的字长相同(参看1.4 节)。
图2-9 说明了两种使用变址的方法。在图 2-9a 中,变址寄存器 R5 中包含一个存储单元的 地址,数值X 定义了一个从这个地址到操作数位置的偏移量(也称为位移量)。在图 2-9b 中具 体说明了所使用的另一种方法。这里,常数X 相当于一个存储器地址,而变址寄存器中的内 容定义了一个到操作数的偏移量。无论在哪种情况下,有效地址都是两个值的和,其中一个值 是在指令中明确给出的,另一个值是保存在一个寄存器中的。
1020
Load R2, 1000(R5)
R5 R5 Load R2, 20(R5)
1020
20 1000
20 =
20 =
1000 1000
b)偏移量在变址寄存器中 a)偏移量作为一个常量给出 偏移量
操作数
操作数 偏移量
图2-9 变址寻址
图2-9 变址寻址