Instruction Addressing Modes
寻址方式回答两个问题:
- 下一条指令在哪里?
- 本条指令要使用的操作数在哪里?
前者称为指令寻址,后者称为数据寻址。在一条指令中,操作码决定“做什么”,寻址方式决定“到哪里找操作对象”。
为什么需要不同寻址方式
指令采用不同寻址方式是为了让指令系统可缩短指令字长,扩大寻址空间,提高编程灵活性。
指令寻址
指令寻址是确定下一条将要执行的指令地址。下一条指令地址始终由程序计数器 PC 给出。
顺序寻址
顺序寻址是默认情况:执行完当前指令后,PC 自动指向下一条指令。
若采用定长指令字结构,且主存按字编址,常写作:
$$
PC \leftarrow (PC) + 1
$$
这里的 1 表示“一个指令字”。若主存按字节编址,且一条指令占 n 字节,则应理解为:
$$
PC \leftarrow (PC) + n
$$
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 设置为新的起始地址即可。
基址寄存器面向系统管理,内容由操作系统设定,对用户程序透明。程序运行过程中,BR 作为基地址一般保持不变;指令中的 A 随不同操作数而变化。
变址寻址用变址寄存器 IX 的内容加上形式地址 A 得到有效地址:
$$
EA=(IX)+A
$$
A 通常保存数组或表的首地址,IX 保存下标偏移量。循环访问数组时,每轮只需修改 IX,指令中的数组首地址不变。
变址寻址通常面向程序员。程序执行过程中,IX 会随循环或下标变化而变化;形式地址 A 常作为基准地址保持不变。
| 对比项 | 基址寻址 | 变址寻址 |
|---|---|---|
| 公式 | $EA=(BR)+A$ | $EA=(IX)+A$ |
| 寄存器含义 | 基地址 | 偏移量 |
形式地址 A |
偏移量 | 基地址 |
| 主要用途 | 程序重定位、多道程序 | 数组、表格、循环访问 |
| 维护者 | 操作系统 | 用户程序 |
相对寻址
相对寻址用程序计数器 PC 的内容加上形式地址 A 得到有效地址:
$$
EA=(PC)+A
$$
其中 A 是相对于 PC 所指地址的位移量,可以为正,也可以为负,用补码表示。
相对寻址广泛用于转移指令。代码整体在程序内部移动时,只要转移目标与当前指令之间的相对距离不变,转移指令的地址码就不需要修改。
相对寻址中的 PC 已经指向下一条指令。也就是说,偏移量 A 是相对于取完当前指令后的 PC计算的,而不是相对于当前指令起始地址计算的。
堆栈寻址中,操作数存放在堆栈中,指令隐含使用堆栈指针 SP 指出栈顶位置。堆栈按后进先出(LIFO)原则管理。
常见栈操作:
PUSH X:把X压入栈顶,并修改SP。POP X:从栈顶弹出一个元素写入X,并修改SP。
若算术指令隐含使用栈顶和次栈顶,则一条零地址加法指令可以理解为:
- 弹出栈顶元素作为一个操作数。
- 再弹出新的栈顶元素作为另一个操作数。
- 完成加法。
- 将结果压回栈顶。
堆栈可以由专用寄存器组实现,也可以由主存中的一段区域实现。前者速度快但成本高,后者成本低但栈操作需要访问主存。
汇总
| 寻址方式 | 有效地址或操作数位置 | 执行阶段访存次数 | 重点用途 |
|---|---|---|---|
| 隐含寻址 | 由操作码或体系结构约定 | 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 隐含指出 |
视实现而定 | 表达式求值、函数调用 |