Ch5 编译原理 地址与寻址

Addressing the addresses

Posted by R1NG on October 27, 2020 Viewed Times

Ch5 地址与寻址

Motivation:

  1. 了解寻址模式
    • 直接寻址模式
    • 寄存器间接寻址模式
    • 基数 + 偏移量寻址模式
  2. ARM 的间接寻址模式

1. 寻址

为了使计算机程序可以高效地访问由大量的存储单元所组合而成的内存 (Memory), 处理器指令集采用内存地址使涉及内存的数据传输操作简易高效.

处理器需要生成一个“地址” (Address) 以引用或参考内存. 这个生成的 “地址” 长度和内存地址一致, 在我们考虑的情形中, 它是 $32$位的, 也就是 $4$ 个字节. 生成 “地址” 的方式又有所不同, 我们将其统称为 “内存寻址” (Memory Addressing). 一些处理器可能只支持部分的内存寻址方式. 在本章中, 我们将介绍: 直接寻址 (Direct Addressing), 寄存器间接寻址 (Register Indirect Addressing) 和直接偏移量寻址 (Immediate Offset Addressing).

1.1 直接寻址

直接寻址又称 立即数寻址立即寻址, 操作数 (地址) 本身包含在指令中, 只要取指令就取到了操作数. 要实现直接寻址, 一条指令必须足够长以容纳这条地址. CISC 处理器一般可以做到这点, 而属于 RISC 指令集的 ARM 架构则不支持这一方式: 显然, 一条 32 位的指令不可能容纳下同为 32 位长的地址. 因此, ARM 使用 间接寻址 (Indirect Addressing).

20201105193412


1.2 寄存器间接寻址 (Register Indirect Addressing)

当前, 我们不妨假设 ARM 架构的 “直接寻址” 是通过 LDR, STR 指令完成的. 例:

1
2
3
4
5
;Operation: a = b + c;
LDR R0, b
LDR R1, c
ADD R0, R0, R1
STR R0, a

这看上去很像直接寻址: 一个 “地址” 被标以某个符号或标签, 并且的确被编码入了指令内.

实际上, 这属于寄存器间接寻址: 允许某个寄存器储存一个 “地址”, 并使用这个被存于寄存器中的 “地址” 来执行数据在内存中的加载和存储.

寄存器间接寻址就是利用寄存器中的数值作为操作数 (地址).

在寄存器间接寻址模式中, 我们需要使用的 “地址” 被存于某个寄存器中. 对于 ARM 架构而言, 由于寄存器的数量较少, 只需要几个位即可表示所有的寄存器. 使用这样的方式, 我们可以将表示寄存器的, 短得多的值编码进指令中, 实现类似于直接寻址的效果.

20201105193442


1.3 直接偏移量寻址 (Immediate Offset Addressing)

直接偏移量寻址属于基址 + 偏移量寻址的一种: 它将某个寄存器值作为基址, 将文字作为偏移量. 显然, 我们可以对寄存器进行操作. 也就是说, 我们可以存储/加载/移动地址, 并且对它进行运算. 一种朴素的思想就是: 我们可以将寄存器内存储的 “地址” 视为二进制整数, 并使用 ADD 这类运算操作指令.

这一寻址方式亦称为 寄存器偏移寻址, 是 ARM 指令集独有的寻址方式, 它是在寄存器寻址得到操作数后, 再进行移位操作, 得到最终的操作数. 在直接偏移量寻址模式下, “地址” 由一个寄存器内所存储的值和一个 “文字”(literal) 计算而来:

20201105193510

LDR/STR 指令的寄存器模式下, ARM 指令集规定我们最多可以使用长为 $12$位的偏移量, 且这些偏移量是可以被执行加法或减法的:

1
2
3
4
LDR R0, [PC, #OFFSET]   ;The offset is the dist from PC to the variable
LDR R0, [R1, #8]
STR R3, [R6, #-0x240]
LDR R7, [R2, #small_constant]

通过使用偏移量, 我们以给定的一个 “基数” 寄存器为起点, 最多可以向前或向后访问大约 2Kb 的内存范围.

下面来看一个基数 + 偏移量寻址的例子:

20201105193539

我们在此对 LDR R0, b 指令所对应的偏移量的计算进行简要解释: ARM 指令集有一特性, 即为程序计数器恒指向下下一条指令的内存地址 (取指-译码-执行三级流水线). 在 LDR R0, b 指令被读取后, 程序计数器会指向第三条指令 ADD R0, R0, R1, 而观察该汇编程序的内存存储结构可以看出, 程序计数器所指向的内存地址和 b 的内存地址之间相差 $4 * 4 = 16$ 个位, 因此偏移量应该是 $16$, 也就得到了图中的结果.

此外, 我们还可以把从另外一个寄存器中读入的值当作偏移量:

20201105193605


2. ARM 间接寻址

2.1 间接寻址模式

我们可以使用使用合适的存储操作指令将一个内存地址存入寄存器中, 从而实现 ARM 架构的间接寻址.

我们可以使用如下指令将内存地址存入寄存器中:

1
2
3
ADR R2, b;      ;move the address of b into R2
LDR R0, [R2]    ;use address in R2 to fetch the value of B

注:

  1. ADR 是一个伪指令. 实际可能需要不止一条指令实现.
  2. ADR 的问题和 LDR 一样: 无法将一条 32 位长的地址编入32 位长的指令中.


2.2 基数 + 偏移量寻址

除了直接偏移量寻址外, ARM 同样允许从另一个寄存器中读入偏移量, 并且读入的偏移量也可以作加减法运算:

20201105193636

总结: ARM 架构的间接寻址模式分类如下:

20201105193709


2.3 前变址寻址方式

前变址寻址, Pre-Indexed Addressing, 指先变址, 再传值的寻址方式:

1
2
; pass value: [SP]+4 to R0
STR R0, [SP, #4]!


2.4 后变址寻址方式

后变址寻址, Post-Indexed Addressing, 指先传值, 再变址的寻址方式:

1
2
; pass value: [SP] to R0, then +4
STR R0, [SP], #4