ARM V8R中汇编指令STR的详细用法
好的,我们来全面、深入地解析 ARMv8-R 架构(通常运行在 AArch32 状态)中 STR (Store Register) 指令的用法。这是与 LDR (Load) 指令相对应的核心内存访问指令,负责将寄存器中的数据写入内存,是CPU与内存交互的关键出⼝。
🧠 核心功能与概述
STR 指令的核心功能是:将源寄存器中的值存储(写入)到内存中的一个指定地址。
- 操作定义:
Memory[Address] = Rt - 主要用途:
- 将计算结果保存到内存中。
- 设置变量的值。
- 配置硬件外设寄存器(通过对内存映射的寄存器进行写入)。
- 在函数调用中,保存临时寄存器的值到栈上。
为了让您快速概览 STR 指令的各种寻址模式,下表总结了其最常见的使用形式:
| 指令示例 | 寻址模式 | 功能描述 | 地址寄存器行为 |
|---|---|---|---|
STR Rt, [Rn] |
基址寻址 | 将 Rt 的值存储到 Rn 指向的地址 |
Rn 不变 |
STR Rt, [Rn, #offset] |
基址+偏移 | 将 Rt 的值存储到 Rn + offset 的地址 |
Rn 不变 |
STR Rt, [Rn, #offset]! |
前变基寻址 | 先将 Rn 更新为 Rn + offset,然后将 Rt 存储到新地址 |
Rn 会改变 |
STR Rt, [Rn], #offset |
后变基寻址 | 先将 Rt 存储到 Rn 的地址,然后将 Rn 更新为 Rn + offset |
Rn 会改变 |
STR Rt, [Rn, Rm] |
寄存器偏移 | 将 Rt 的值存储到 Rn + Rm 的地址 |
Rn 不变 |
STRB Rt, [Rn] |
存储字节 | 仅将 Rt 的最低8位存储到内存 |
|
STRH Rt, [Rn] |
存储半字 | 仅将 Rt 的最低16位存储到内存 |
⚙️ 语法与操作数格式
STR 指令的通用语法如下:
STR{<size>}{<cond>} <Rt>, [<Rn>, <offset>]{!}
STR{<size>}{<cond>} <Rt>, [<Rn>], <offset>
{<size>}:可选的数据大小指定符。- (空):默认,存储字 (Word, 32位)
B:存储字节 (Byte, 8位)。使用STRB。H:存储半字 (Halfword, 16位)。使用STRH。
{<cond>}:可选的条件码后缀(如EQ,NE)。<Rt>:源寄存器,其值将被写入内存。<Rn>:基址寄存器,其值是内存访问的基地址。<offset>:偏移量。可以是:- 一个立即数(如
#12)。 - 一个寄存器(如
Rm)。 - 一个移位操作的寄存器(如
Rm, LSL #2)。
- 一个立即数(如
{!}:写回后缀。如果使用!,则操作完成后,计算后的新地址会被写回基址寄存器Rn。
🛠️ 详细用法与示例
1. 基本存储:将数据写入变量或绝对地址
这是最直接的用法,将寄存器的值保存到另一个寄存器所指向的内存位置。
LDR R0, =my_variable @ 将变量 'my_variable' 的地址加载到 R0
MOV R1, #0x42 @ 准备要写入的数据
STR R1, [R0] @ 将 R1 中的值 (0x42) 写入到 R0 所指向的内存地址
@ 即:my_variable = 0x42
2. 访问数组或结构体成员(基址+偏移)
通过基址加偏移的方式,可以高效地访问数组元素或结构体成员。
@ 假设 R0 是数组基地址,我们要写入 array[2] (假设每个元素占4字节)
MOV R1, #0x12345678 @ 要写入的值
STR R1, [R0, #8] @ 地址 = R0 + 8 (2 * 4 bytes)。R0 的值保持不变。
@ 访问结构体 (假设 R0 是结构体指针,第二个成员在偏移量 #12 处)
STR R2, [R0, #12] @ 将 R2 写入结构体的第二个成员
3. 后变基寻址:写入后自动移动指针
这种模式在连续写入内存块(如初始化数组、填充缓冲区)时极其高效。
MOV R0, =buffer_start @ R0 指向缓冲区起始地址
MOV R1, #0xA @ 要写入的数据 A
MOV R2, #0xB @ 要写入的数据 B
STR R1, [R0], #4 @ 步骤 1: 将 R1 写入 [R0] (地址A), 然后将 R0 = R0 + 4 (指向地址B)
STR R2, [R0], #4 @ 步骤 2: 将 R2 写入 [R0] (地址B), 然后将 R0 = R0 + 4 (指向地址C)
@ 执行后,内存中:
@ buffer_start: 0xA
@ buffer_start+4: 0xB
@ 且 R0 指向 buffer_start+8
4. 前变基寻址:先移动指针,再写入
同样用于连续写入,但逻辑是“先准备好下一个地址,再存数据”。
MOV R0, =buffer_start
MOV R1, #0xA
MOV R2, #0xB
STR R1, [R0, #4]! @ 步骤 1: 先将 R0 更新为 R0 + 4 (指向地址B),然后将 R1 存入 [R0] (地址B)
STR R2, [R0, #4]! @ 步骤 2: 先将 R0 更新为 R0 + 4 (指向地址C),然后将 R2 存入 [R0] (地址C)
@ 效果与后变基类似,但地址计算时机不同。
5. 存储不同大小的数据(STRB/STRH)
当只需要存储数据的一部分时,使用 STRB 或 STRH。
MOV R0, =byte_data
MOV R1, =0x12345678
STRB R1, [R0] @ 只将 R1 的最低 8 位 (0x78) 写入内存
MOV R0, =halfword_data
STRH R1, [R0] @ 只将 R1 的最低 16 位 (0x5678) 写入内存
6. 配置硬件寄存器(内存映射I/O)
在嵌入式系统中,外设寄存器被映射到特定的内存地址。通过 STR 指令写入这些地址即可配置硬件。
@ 假设 GPIO 数据寄存器的地址是 0x40020000
LDR R0, =0x40020000 @ 将外设寄存器地址加载到 R0
MOV R1, #1 @ 准备要配置的值:点亮 LED
STR R1, [R0] @ 将值写入 GPIO 寄存器,硬件随即响应此操作
⚠️ 重要注意事项与原理
-
地址对齐:
STR:要求地址是 4 字节对齐的。STRH:要求地址是 2 字节对齐的。STRB:可以对任何地址进行操作。- 非对齐访问可能会导致性能下降或触发硬件异常,具体取决于系统配置。
-
偏移量范围:
- 立即数偏移量
offset有其取值范围(例如 ±4095 字节)。对于更大的偏移,需要先计算地址到寄存器中。
- 立即数偏移量
-
写回操作 (
!):- 使用
!后缀会更新基址寄存器Rn。这在遍历数组或缓冲区时非常有用,可以节省一条ADD指令。 - 如果不使用
!,则Rn的值保持不变。
- 使用
-
访问权限与内存类型:
- 你只能向具有写权限的内存区域进行存储操作。尝试向只读内存(如代码段)写入会触发异常。
- 写入设备内存(如硬件寄存器)可能会有副作用(例如清空中断标志),并且其行为可能受内存类型属性影响(通常需要配合
DSB指令确保写入完成)。
-
内存屏障:
在写入控制寄存器或与DMA控制器等其它硬件共享的内存时,有时需要在STR指令后使用数据同步屏障 (DSB) 指令,以确保存储操作在执行后续指令之前已完成并对系统中所有组件可见。STR R0, [R1] @ 配置一个关键的控制寄存器 DSB SY @ 等待存储操作彻底完成 @ 后续指令
💎 总结
STR 指令是 ARM 汇编中将数据从CPU核心输出到内存系统的基础工具。
它的核心价值在于:
- 数据持久化:将寄存器中的临时计算结果保存到内存变量中。
- 硬件控制:通过内存映射I/O配置和控制外部设备,这是嵌入式编程的核心。
- 高效数据移动:通过各种灵活的寻址模式(特别是前变基和后变基),高效地处理数组、缓冲区和数据结构。
简单决策指南:
- 需要存储32位数据 -> 使用
STR - 需要存储16位数据 -> 使用
STRH - 需要存储8位数据 -> 使用
STRB - 需要连续存储(如数组处理)-> 使用前变基
[Rn, #offset]!或后变基[Rn], #offset寻址模式 - 在配置硬件寄存器后 -> 考虑使用
DSB指令确保写入生效
掌握 STR 指令的各种寻址模式和使用场景,是进行高效底层编程、驱动开发和系统设计的必备技能。
浙公网安备 33010602011771号