寻址方式回答两个问题:

  • 下一条指令在哪里?
  • 本条指令要使用的操作数在哪里?

前者称为指令寻址,后者称为数据寻址。在一条指令中,操作码决定“做什么”,寻址方式决定“到哪里找操作对象”。

为什么需要不同寻址方式

指令采用不同寻址方式是为了让指令系统可缩短指令字长,扩大寻址空间,提高编程灵活性

指令寻址

指令寻址是确定下一条将要执行的指令地址。下一条指令地址始终由程序计数器 PC 给出。

指令寻址

顺序寻址

顺序寻址是默认情况:执行完当前指令后,PC 自动指向下一条指令。

若采用定长指令字结构,且主存按字编址,常写作:

$$
PC \leftarrow (PC) + 1
$$

这里的 1 表示“一个指令字”。若主存按字节编址,且一条指令占 n 字节,则应理解为:

$$
PC \leftarrow (PC) + n
$$

Note

PC 实际增加多少,取决于指令长度和主存编址方式。

若采用变长指令字结构,CPU 取到指令开头后,需要根据操作码判断这条指令总长度,再把 `PC` 改为下一条指令的起始地址。

跳跃寻址

跳跃寻址由转移类指令指出下一条指令地址。CPU 取指后仍会形成顺序地址(即下一条顺序指令的地址),但执行转移指令时,更新后PC 会被改写为转移目标。

例如无条件转移 JMP 7 的效果是:

$$
PC \leftarrow 7
$$

条件转移则需要先根据条件判断是否改写 PC。若条件成立,PC 改为转移目标;若条件不成立,继续使用顺序地址。

数据寻址

数据寻址是确定本条指令中地址码所指操作数的真实位置。地址码字段给出的地址通常称为形式地址 A;根据寻址方式计算出的真实地址称为有效地址 EA

一条指令的地址部分常可理解为:

1
寻址特征 + 形式地址 A

寻址特征说明如何解释 A,EA 才是最终访问操作数时使用的地址。

基本数据寻址方式

常见数据寻址方式

直接寻址

直接寻址中,指令地址码给出的形式地址就是操作数所在主存单元的有效地址:

$$
EA=A
$$

取到指令后,执行阶段只需要按 A 访问一次主存即可取到操作数。

  • 优点:简单,地址计算开销小。
  • 缺点:寻址范围受地址码位数限制;操作数地址写死在指令中,不便修改。

间接寻址

间接寻址中,形式地址 A 指向的主存单元中存放的不是操作数,而是操作数的有效地址:

$$
EA=(A)
$$

执行时先访问 A 所在主存单元,读出真正的地址 EA,再访问 EA 取操作数。

  • 优点:有效地址的位数可由存储字长决定,能扩大寻址范围;地址可通过修改指针单元改变。
  • 缺点:一次间接寻址在执行阶段至少需要两次访存,速度较慢。

如果允许多级间接寻址,则还要继续按读出的地址再找下一层地址,直到得到真正的操作数地址。

寄存器寻址

寄存器寻址中,地址码字段直接给出寄存器编号,操作数就在该寄存器中:

$$
EA=R_i
$$

  • 优点:执行阶段不访问主存,速度快;寄存器编号位数通常比主存地址短,指令也可更短。
  • 缺点:寄存器数量有限。

寄存器间接寻址

寄存器间接寻址中,地址码字段给出寄存器编号,寄存器中保存操作数所在主存单元的地址:

$$
EA=(R_i)
$$

执行阶段需要按寄存器中的地址访问一次主存取操作数。

它和普通间接寻址的区别在于:普通间接寻址先访问主存取地址;寄存器间接寻址直接从寄存器取地址,因此少一次主存访存。

隐含寻址

隐含寻址不在指令中显式给出操作数地址,而由操作码或体系结构约定操作数位置。

例如某些一地址指令默认另一个操作数在 ACC 中:

$$
(ACC)\ OP\ (A) \rightarrow ACC
$$

堆栈机中的零地址算术指令也常隐含使用栈顶和次栈顶作为操作数。

  • 优点:可减少地址字段,缩短指令。
  • 缺点:操作数位置不够灵活,需要理解体系结构约定。

立即寻址

立即寻址中,形式地址字段 A 本身就是操作数,通常称为立即数

$$
操作数=A
$$

汇编中常用 # 标记立即数,例如 #5 表示常数 5

  • 优点:执行阶段不需要为取操作数访问主存,速度快。
  • 缺点:立即数大小受地址字段位数限制;它只能作为源操作数,不能作为结果写回位置。

偏移寻址

基址寻址、变址寻址和相对寻址都属于偏移寻址。它们共同的形式是:

$$
EA=某个基准值 + A
$$

偏移寻址对比

基址寻址

基址寻址用基址寄存器 BR 的内容加上形式地址 A 得到有效地址:

$$
EA=(BR)+A
$$

BR 保存某个存储区域的起始地址,A 是相对于该起始地址的偏移量。

基址寻址的典型作用是支持程序重定位。程序装入主存的位置可能变化,但指令中的偏移量可以不变,只要把 BR 设置为新的起始地址即可。

Important

基址寄存器面向系统管理,内容由操作系统设定,对用户程序透明。程序运行过程中,BR 作为基地址一般保持不变;指令中的 A 随不同操作数而变化。

### 变址寻址

变址寻址用变址寄存器 IX 的内容加上形式地址 A 得到有效地址:

$$
EA=(IX)+A
$$

A 通常保存数组或表的首地址,IX 保存下标偏移量。循环访问数组时,每轮只需修改 IX,指令中的数组首地址不变。

Note

变址寻址通常面向程序员。程序执行过程中,IX 会随循环或下标变化而变化;形式地址 A 常作为基准地址保持不变。

### 基址寻址与变址寻址的区别
对比项 基址寻址 变址寻址
公式 $EA=(BR)+A$ $EA=(IX)+A$
寄存器含义 基地址 偏移量
形式地址 A 偏移量 基地址
主要用途 程序重定位、多道程序 数组、表格、循环访问
维护者 操作系统 用户程序

相对寻址

相对寻址用程序计数器 PC 的内容加上形式地址 A 得到有效地址:

$$
EA=(PC)+A
$$

其中 A 是相对于 PC 所指地址的位移量,可以为正,也可以为负,用补码表示。

Warning
计算$EA$时, 需要先将$A$符号扩展,再类型提升为符号数才能与$(PC)$相加。

相对寻址广泛用于转移指令。代码整体在程序内部移动时,只要转移目标与当前指令之间的相对距离不变,转移指令的地址码就不需要修改。

Important

相对寻址中的 PC 已经指向下一条指令。也就是说,偏移量 A 是相对于取完当前指令后的 PC计算的,而不是相对于当前指令起始地址计算的。

## 堆栈寻址

堆栈寻址中,操作数存放在堆栈中,指令隐含使用堆栈指针 SP 指出栈顶位置。堆栈按后进先出(LIFO)原则管理。

常见栈操作:

  • PUSH X:把 X 压入栈顶,并修改 SP
  • POP X:从栈顶弹出一个元素写入 X,并修改 SP

若算术指令隐含使用栈顶和次栈顶,则一条零地址加法指令可以理解为:

  1. 弹出栈顶元素作为一个操作数。
  2. 再弹出新的栈顶元素作为另一个操作数。
  3. 完成加法。
  4. 将结果压回栈顶。

堆栈可以由专用寄存器组实现,也可以由主存中的一段区域实现。前者速度快但成本高,后者成本低但栈操作需要访问主存。

汇总

寻址方式 有效地址或操作数位置 执行阶段访存次数 重点用途
隐含寻址 由操作码或体系结构约定 0 缩短指令
立即寻址 A 本身是操作数 0 常数操作数
直接寻址 $EA=A$ 1 简单访问主存
一次间接寻址 $EA=(A)$ 2 扩大寻址范围、指针
寄存器寻址 操作数在 $R_i$ 0 快速访问寄存器
寄存器间接寻址 $EA=(R_i)$ 1 指针在寄存器中
基址寻址 $EA=(BR)+A$ 1 程序重定位
变址寻址 $EA=(IX)+A$ 1 数组、循环
相对寻址 $EA=(PC)+A$ 1 转移指令、代码浮动
堆栈寻址 SP 隐含指出 视实现而定 表达式求值、函数调用