在ARM处理器中,寻址模式决定了CPU如何计算内存地址以访问数据。作为RISC架构的代表,ARM采用了加载-存储(Load-Store)架构设计,这意味着所有数据处理指令都只能在寄存器间操作,只有专门的加载(LDR)和存储(STR)指令才能访问内存。这种设计带来了几个关键特性:
ARMv5架构中,LDR和STR指令支持9种寻址模式,这些模式主要通过以下几种方式组合而成:
提示:在ARM汇编中,方括号[]表示内存访问,如
[R1]表示访问R1寄存器中保存的地址处的数据。
LDR/STR指令的通用语法格式如下:
code复制LDR|STR{<cond>}{B}{T} <Rd>, <addressing_mode>
其中各字段含义:
cond:条件执行后缀(如EQ、NE等)B:字节访问标志(B=1访问字节,B=0访问字)T:用户模式访问标志Rd:目标/源寄存器addressing_mode:指定9种寻址模式之一根据偏移量类型和索引方式,9种寻址模式可分为三大类:
| 类型 | 偏移形式 | 前索引 | 后索引 |
|---|---|---|---|
| 立即数偏移 | #+/-<offset_12> |
[Rn, #+/-<offset_12>]! |
[Rn], #+/-<offset_12> |
| 寄存器偏移 | +/-<Rm> |
[Rn, +/-<Rm>]! |
[Rn], +/-<Rm> |
| 缩放寄存器偏移 | +/-<Rm>, <shift> #<shift_imm> |
[Rn, +/-<Rm>, <shift> #<shift_imm>]! |
[Rn], +/-<Rm>, <shift> #<shift_imm> |
此外还有基本的偏移寻址(不写回基址寄存器):
[Rn, #+/-<offset_12>][Rn, +/-<Rm>][Rn, +/-<Rm>, <shift> #<shift_imm>]语法:[<Rn>, #+/-<offset_12>]
操作:
armasm复制if U == 1 then
address = Rn + offset_12
else
address = Rn - offset_12
end
编码格式:
code复制31-28 27-25 24 23 22 21 20 19-16 15-12 11-0
cond 010 P U B W L Rn Rd offset_12
典型应用场景:
armasm复制LDR R1, [R2, #8] ; 读取R2+8地址处的字到R1
STRB R3, [R4, #-12] ; 存储R3的低字节到R4-12地址
语法:[<Rn>, #+/-<offset_12>]!
特点:先计算地址并写回Rn,然后用新地址访问内存
操作流程:
示例:
armasm复制MOV R1, #0x1000
LDR R2, [R1, #4]! ; R1变为0x1004,然后加载0x1004处的值到R2
语法:[<Rn>], #+/-<offset_12>
特点:先用原Rn值访问内存,再更新Rn
操作流程:
示例(数组遍历):
armasm复制MOV R1, #0x1000 ; 数组起始地址
MOV R2, #0 ; 初始化计数器
loop:
LDR R3, [R1], #4 ; 读取当前元素并自动指向下一个
ADD R2, R2, #1
CMP R2, #10
BNE loop
语法:[<Rn>, +/-<Rm>]
操作:
armasm复制if U == 1 then
address = Rn + Rm
else
address = Rn - Rm
end
编码特点:
典型应用(结构体访问):
armasm复制MOV R1, #base_addr
MOV R2, #offset
LDR R3, [R1, R2] ; 相当于R3 = *(base_addr + offset)
语法:[<Rn>, +/-<Rm>, <shift> #<shift_imm>]
支持五种移位操作:
操作流程:
示例(数组访问):
armasm复制MOV R1, #array_base
MOV R2, #index
LDR R3, [R1, R2, LSL #2] ; 访问array_base + index*4
指令编码中的控制位决定寻址行为:
| 位域 | 名称 | 功能 |
|---|---|---|
| P[24] | Pre/Post索引 | 0=后索引,1=前索引/偏移 |
| U[23] | 加减标志 | 0=减,1=加 |
| B[22] | 字节/字 | 0=字,1=字节 |
| W[21] | 写回标志 | 与P位配合决定是否更新基址 |
| L[20] | 加载/存储 | 0=STR,1=LDR |
code复制offset_12[11:0]:12位有符号立即数(实际范围0-4095)
code复制Rm[3:0]:偏移寄存器编号
shift=00, shift_imm=0
code复制shift_imm[7:4]:移位量
shift[1:0]:移位类型
Rm[3:0]:偏移寄存器
使用立即数偏移高效访问结构体字段:
armasm复制; 假设结构体:
; struct {
; int a; @ +0
; char b; @ +4
; short c; @ +5
; }
LDR R1, [R0, #0] ; 读取a
LDRB R2, [R0, #4] ; 读取b
LDRH R3, [R0, #5] ; 读取c
后索引模式简化循环控制:
armasm复制; 清零100个字的内存区域
MOV R0, #base_addr
MOV R1, #100
MOV R2, #0
loop:
STR R2, [R0], #4 ; 存储并自动前进指针
SUBS R1, R1, #1
BNE loop
当Rn为R15(PC)时:
示例:
armasm复制LDR R0, [PC, #offset] ; 从PC+8+offset处加载数据
通过T后缀实现用户模式内存访问:
armasm复制LDRT R0, [R1], #4 ; 以用户权限执行加载
| 特性 | ARMv5 | ARMv6+ |
|---|---|---|
| Rn=Rm限制 | 结果不可预测 | 允许相同寄存器 |
| 非对齐访问 | 通常不支持 | 支持部分非对齐访问 |
| 双字访问 | 不支持LDRD/STRD | 支持 |
所有寻址模式都支持条件执行,但需注意:
示例:
armasm复制LDREQ R0, [R1], #4 ; 仅当EQ条件满足时执行
armasm复制LDR R0, [R1], #4 ; 正确:后索引
LDR R0, [R1, #4] ; 错误:R1不会更新
armasm复制LDR R0, [R1, #4]! ; 使用前地址+4
LDR R0, [R1], #4 ; 使用原地址,然后+4
armasm复制LDR R1, [R1, #4]! ; 危险:同时使用和修改R1
通过修改B位实现不同宽度访问:
示例:
armasm复制LDRB R1, [R2] ; 加载字节
STRH R3, [R4] ; 存储半字
虽然不属于LDR/STR,但相关的LDM/STM指令提供批量内存访问:
armasm复制LDMIA R1!, {R2-R5} ; 连续加载多个寄存器
ARM的三级流水线(取指-解码-执行)导致:
不同的寻址模式对缓存的影响:
优化建议:
armasm复制; 不好的示例:随机访问
LDR R0, [R1, R2, LSL #2] ; R2变化大可能导致缓存失效
; 好的示例:顺序访问
LDR R0, [R1], #4 ; 线性访问
利用多寄存器传输和自动增量:
armasm复制copy_loop:
LDMIA R1!, {R2-R5} ; 一次加载4个字
STMIA R0!, {R2-R5}
SUBS R6, R6, #16 ; 每次处理16字节
BGT copy_loop
结合多种寻址模式:
armasm复制; R0=哈希值, R1=表基址
AND R2, R0, #0xFF ; 取低8位
LDR R3, [R1, R2, LSL #2] ; 表项大小4字节
GAS支持的扩展语法:
armasm复制ldr r0, [r1, r2] ; 寄存器偏移
ldr r0, [r1, r2, lsl #2] ; 缩放偏移
ldr r0, [r1], #4 ; 后索引
理解反汇编输出:
code复制e5910004 ldr r0, [r1, #4] ; 立即数偏移
e7910102 ldr r0, [r1, r2, lsl #2] ; 缩放寄存器
e4910004 ldr r0, [r1], #4 ; 后索引
关键内存操作前应验证地址:
armasm复制; 检查R1是否在合法范围内
CMP R1, #LOWER_BOUND
BLO error
CMP R1, #UPPER_BOUND
BHI error
LDR R0, [R1]
合理使用用户模式访问(T后缀)保护系统内存:
armasm复制STRT R0, [R1] ; 用户模式存储,触发权限检查
虽然ARMv5的寻址模式已经非常成熟,但在新架构中:
但核心的LDR/STR寻址理念保持一致,理解这些基础模式对掌握新架构至关重要。