想象你开了一家小型图书馆,每天要处理大量的“取书”和“还书”操作。当你需要看一本书时,会走到书架前(内存),把书拿下来放到自己的书桌(寄存器)上;当你读完时,再把书从书桌放回书架。这个“取”与“放”的动作,就是ARM处理器中最基础也最重要的两个指令——LDR(Load,加载)和STR(Store,存储)。 整个计算机世界就像这座图书馆:寄存器是离你最近的临时工作台,内存是巨大的书架阵列。CPU 的速度可比你从书架取书快多了——它每秒能执行几十亿次操作,但每次从内存取数据,都要经历“走出工作台→在书架间找到位置→取出→走回来”这一整套流程。LDR 和 STR 就是这套流程的标准化动作,而前变址、后变址、LDM/STM 这些扩展模式,就像你优化取书路线、一次搬多本书的聪明技巧。
理解 LDR/STR 是读懂 ARM 汇编的敲门砖。无论你是做嵌入式开发、系统底层优化,还是 AI 芯片的算子库调优,读写内存都是最频繁的操作。掌握它,你就掌握了程序运行的本质——数据在处理器和存储器之间的循环流动。
可视化演示
\u901a\u8fc7\u4e09\u9636\u6bb5\u4ea4\u4e92\u5c55\u793aLDR/STR\u57fa\u672c\u6570\u636e\u6d41\u52a8\u3001\u4e09\u79cd\u5bfb\u5740\u6a21\u5f0f\u5bf9\u6bd4\u4e0eLDM/STM\u6279\u91cf\u5bc4\u5b58\u5668\u64cd\u4f5c\uff0c\u8986\u76d6\u5730\u5740\u8ba1\u7b97\u4e0e\u56de\u5199\u673a\u5236\u3002
本节配有交互动效(进入视口后自动加载,约 3KB)。
LDR/STR\u57fa\u672c\u64cd\u4f5c \u5185\u5b58\u52a0\u8f7d \u5bc4\u5b58\u5668\u5b58\u50a8 Rd\u2190Mem[Rn+off] \u5730\u5740\u7531\u57fa\u5740\u52a0\u504f\u79fb\u8ba1\u7b97
LDR加载指令
图书馆管理员小林接到了一个新的取书任务:第 3 号书架第 2 层有一本书需要拿到工作台上。他记下书的位置(地址),径直走向书架,取下书,放回工作台。这个过程就是 LDR 指令的“人生”——LDR(Load Register)把内存中的数据读入寄存器,方向只能用“内存 → 寄存器”一个箭头表示,就像取书的方向只能是“书架 → 工作台”一样。
在 ARM 汇编中,LDR 的典型写法是 LDR R0, [R1],意思是:把 R1 寄存器中存储的地址所指的内存单元里的数据,加载到 R0 寄存器中。R1 在这里扮演“索书单”的角色——它不直接装数据,而是装数据所在的位置(地址)。这种“寄存器指路,目标寄存器收数据”的设计,就是寻址的精髓。
LDR 还有几个变体:LDRB 只加载一个字节,LDRH 加载半个字(16 位),LDRSB 和 LDRSH 则会在加载后进行符号扩展。好比取书时你可以只取一章(一个字节)、半本(半字),或者根据需求决定是否把“欠费标记”(符号位)一并保留。
回到图书馆的比喻:LDR 这条指令的执行,就是小林拿着索书单(R_n),走到对应的书架格子,把书取出来放到工作台(R_d)上的过程。他永远不会反向操作——不会把工作台上的书放回书架,那是 STR 的工作。
- 目标寄存器R_d 是数据的目的地,好比工作台上放书的位置。
- 源地址寄存器R_n 存的是内存地址,好比索书单上书架编号。
- 方向标记箭头指向 R_d,表示数据流动方向是内存到寄存器,反向则非法。
STR存储指令
书看完了,工作台上堆满了书,小林要把其中一本放回书架。他拿起书,走到对应的书架位置,放了回去。这就是 STR(Store Register)的故事——STR 把寄存器中的数据存储到内存中,方向与 LDR 完全相反:“寄存器 → 内存”。
STR 的语法同样直观:STR R0, [R1],意思是将 R0 寄存器中的数据,存储到 R1 寄存器所指的内存地址中。R0 是“手中的书”,R1 是“索书单”。要特别注意的是,LDR 和 STR 的方向不能搞反——就像你不能把“从书架取书”说成“把书放回书架”,LDR 永远不会把数据从寄存器写到内存,STR 也永远不会把数据从内存读到寄存器。
STR 同样有字节和半字变体:STRB 存储一个字节,STRH 存储 16 位半字。此外,STR 在偏移量应用上非常灵活,可以用立即数偏移(如 STR R0, [R1, #4],往 R1 地址往后 4 个字节的位置存数据),也可以用寄存器偏移(如 STR R0, [R1, R2],往 R1 加上 R2 得到的地址处存数据),这好比把书放到“第 3 排书架往右数 2 格”的位置。
用图书馆的话说:STR 就是你手中的书(R_d)按照索书单(R_n),放在某个书架(地址 = R_n + 偏移量)上的过程。你永远不会让这个过程反向发生——从书架上“拿”书是 LDR 的活儿。
- 数据源寄存器R_d 持有要存储的数据,是“书”。
- 基址寄存器R_n 提供起始地址,是“索书单”的基础。
- 偏移量offset 可以是立即数或寄存器,表示在基址基础上偏移的位置。
前变址模式
小林发现,每天整理书架时,他要按照入库单的顺序,把一本本书放到连续的位置上:第 1 本书放 0 号位,第 2 本放 1 号位,第 3 本放 2 号位……如果每放一本书都要重新算一遍位置,效率太低了。于是他学了个聪明办法:先算出新位置,放好书,然后更新自己的索书单——把索引加 1,下次直接按新索书单找位置。这就是前变址(Pre-indexed)模式。
前变址的写法是 LDR R0, [R1, #4],含义是:先用 R1 的值加上 4 得到有效地址,再对这个地址进行访问。如果你希望访问完之后,R1 也跟着更新成这个新地址,就在后面加一个感叹号:LDR R0, [R1, #4]!。带 ! 的写法叫“前变址回写”,意思是“用新索书单替换旧索书单”。
前变址最典型的应用是数组遍历。假设你有一个整数数组,基址存在 R1 中,你想依次读取第 0、1、2 个元素。每次取完一个元素后,地址自动加 4(一个字的大小),R1 自动指向下一个元素。这样循环体里只需要一条 LDR 指令,简洁高效。
前变址模式就像小林“先走到新位置,再把书放下,同时更新索书单”。下次再操作时,他不需要重新计算,直接按照更新后的索书单就能找到正确的位置。这种模式在遍历数组、访问结构体字段时特别高效——一条指令同时完成了“找位置”和“更新索书单”两件事。
! 时回写基址寄存器。- 计算有效地址基址 + 偏移,得到要访问的地址。
- 访问数据用上一步算出的地址访问内存。
- 回写基址(可选)如果指令带
!,把新地址写回 R_n。
后变址模式
有时候,小林的整理方式不一样:他先按手里的索书单把书放到当前位置,放完之后,再更新索书单指向下一个位置。这个顺序与前变址正好相反,叫做后变址(Post-indexed)模式。就好比你在一张便签上写下“当前放到第 3 格”,放好书之后,再把便签改成“第 4 格”。下一次按便签找的时候,已经是新的位置了。
后变址的写法是 LDR R0, [R1], #4,注意没有感叹号,但偏移量写在了方括号外面。含义是:先用 R1 的值作为有效地址访问内存,访问结束后 R1 自动加上 4。基址寄存器的更新是强制发生的,不需要 ! 来标记。
后变址的一个经典应用场景是栈操作。栈的本质就是一段连续内存,栈指针 SP(Stack Pointer)指向栈顶。压栈操作(相当于把数据存入栈)可以用 STR R0, [SP], #-4 来实现:先把 R0 的值存到当前 SP 指向的位置,然后 SP 自动减去 4,指向新的栈顶。出栈则用 LDR R0, [SP], #4:先从当前 SP 位置取数据,再把 SP 加上 4。
用图书馆比喻:后变址是“先按现在的索书单放好书,再算好下次的位置并更新索书单”。它与前变址不同——前变址是先更新索书单再找位置,后变址是先找位置放好书再更新索书单。这个顺序差异在栈操作中至关重要:栈指针必须“先用后移”或者“先移后用”,顺序搞反就会覆盖数据。
- 访问当前地址直接使用 R_n 当前值作为地址访问内存。
- 更新基址寄存器访问完成后,R_n 自动加上偏移量。
LDM/STM批量
小林收到一个紧急任务:要把一整排书架上的 10 本书全部搬到工作台上。如果他一本一本搬,效率太低。聪明的他找来了一辆小推车,一次把 10 本书全推过来——这就是 LDM/STM 批量传输指令的智慧。LDM(Load Multiple)一次从内存加载多个寄存器的值,STM(Store Multiple)一次把多个寄存器的值存储到内存。
LDM 的写法如 LDMIA R0!, {R1-R4},含义是:从 R0 指向的地址开始,连续读取 4 个字,分别存入 R1、R2、R3、R4。后面的 IA 表示 "Increment After",即每次访问后地址自动增加 4。R0 后的感叹号表示基址寄存器会被更新。STM 的写法类似:STMIA R0!, {R1-R4}。
批量传输最大的价值是现场保护。当发生中断或函数调用时,CPU 需要保存当前所有寄存器的值(好比离开图书馆前把桌面文件归档),以便处理完回来还能继续原来的工作。STM 一次就能把所有需要保存的寄存器推入栈中,LDM 再一次性恢复,大大减少了指令条数。
小推车的比喻完美诠释了 LDM/STM:单条 LDR/STR 就像一本一本地搬书,而 LDM/STM 则是一车把所有书推走。在中断处理和函数调用的场景中,寄存器里的数据就是“桌面上的重要文件”,必须快速归档(STM)并快速恢复(LDM),批量指令正是为此而生。
- 寄存器列表花括号内列出要加载/存储的寄存器。
- 连续访问从基址开始逐字访问,每次地址加 4。
- 基址更新带
!时,R_n 更新为最后访问的地址。
非对齐访问
小林注意到一个奇怪的现象:大多数书架上的书都对齐摆放,每本书占一个格子,书从格子开头开始放。但偶尔有一本书放歪了,占了两个格子的边界。对于处理器来说,内存也有类似的“对齐”规则——如果一个 32 位字的地址是 4 的倍数(即地址最低 2 位为 0),就说它是对齐的;否则就是非对齐的。
在 ARM 架构中,早期的处理器严格禁止非对齐访问,一旦遇到非对齐地址,直接引发异常(类似图书馆管理员发现书放错位置,立即上报处理)。新一些的 ARM 处理器增加了非对齐访问支持:硬件自动把一次非对齐的字访问拆成两次对齐访问,对程序员透明——好比管理员自己悄悄把歪书扶正,不影响其他人。
但是,并非所有指令都支持非对齐访问。LDM、STM、LDRD、STRD 等指令要求地址必须对齐,如果地址非对齐,无论处理器是否支持非对齐访问,都会引发异常。此外,LDRH/STRH(半字访问)要求地址是 2 的倍数。这是因为批量传输的硬件逻辑太复杂,拆分会严重影响效率。
回到图书馆比喻:对齐的书整整齐齐排成一条线,管理员一眼就能找到;非对齐的书横跨格子边界,要么系统支持自动处理(硬件拆分成两次格子内的操作),要么就上报异常让上级解决。对于嵌入式开发者来说,尽量保证地址对齐是避免踩坑的最好策略——就像你总希望图书馆的书都放得整整齐齐。
- 对齐判定地址对字大小取模,结果为 0 则对齐。
- 硬件拆分非对齐访问被拆分为多次对齐访问。
AI 应用场景
场景一:DMA 控制器中的描述符搬运
阶段:嵌入式系统底层驱动开发
解决的问题:DMA(直接内存访问)控制器的描述符表包含多个字段(源地址、目的地址、长度、控制标志等)。如果逐字段读取效率极低,还容易因非对齐访问触发异常。用 LDR/STR 配合 LDM/STM 可以一次将整个描述符载入或存储。
具体案例:在 AI 推理芯片的驱动代码中,CPU 需要将计算任务描述符写入内存,DMA 才能执行搬运。通过 STM 一条指令把描述符的 4 个字段一次性写入,再用 LDR 验证回读,确保数据正确。这种模式在 NVDLA 等 NPU 驱动中很常见。
场景二:AI 算子库中的张量数据搬运
阶段:AI算子计算加速
解决的问题:AI 模型计算涉及大量张量数据在内存与寄存器之间的搬运。在 ARM Cortex-A 系列处理器上编写 NEON 优化代码时,需要先用 LDR 把张量数据从内存加载到寄存器,计算完再用 STR 存回内存。理解 LDR/STR 的寻址模式,直接影响数据搬运效率。
具体案例:在实现一个 3×3 卷积算子时,需要反复加载图像像素块到寄存器。用前变址模式 LDR Q0, [R1, #16]! 可以一次加载 16 字节并自动更新基址,使得内层循环只有一条 LDM 指令就能完成数据加载,显著提升算子性能。
场景三:RTOS 上下文切换中的现场保护
阶段:实时操作系统任务调度
解决的问题:实时操作系统(RTOS)的多任务调度需要保存和恢复每个任务的寄存器现场。用 STM 一次保存所有寄存器到任务栈,用 LDM 从任务栈恢复,确保任务切换时数据不丢失。
具体案例:FreeRTOS 的上下文切换代码(PendSV_Handler)正是使用 STMDB SP!, {R4-R11} 保存任务现场,用 LDMIA SP!, {R4-R11} 恢复现场。这一对批量访问指令是整个 RTOS 任务切换的基础。
Python 演示
"""模拟 ARM LDR/STR 指令行为演示前变址与后变址模式下寄存器和内存的变化""" class ARMSimulator: def __init__(self): # 内存用字典表示,键为地址 self.memory = {} # 寄存器组,R0-R15 self.regs = [0] * 16 self.free_lists = {} def ldr(self, rd, rn, offset=0, pre=False, writeback=False): """LDR: 从内存加载到寄存器 pre=True时为前变址,pre=False时为后变址 writeback=True时更新基址寄存器""" addr = self.regs[rn] + offset if pre else self.regs[rn] self.regs[rd] = self.memory.get(addr, 0) if pre and writeback: self.regs[rn] = addr elif not pre: # 后变址强制更新基址 self.regs[rn] = self.regs[rn] + offset return self.regs[rd] def str(self, rd, rn, offset=0, pre=False, writeback=False): """STR: 从寄存器存储到内存 pre=True时为前变址,pre=False时为后变址""" addr = self.regs[rn] + offset if pre else self.regs[rn] self.memory[addr] = self.regs[rd] if pre and writeback: self.regs[rn] = addr elif not pre: self.regs[rn] = self.regs[rn] + offset return addr def ldm(self, rn, reg_list, writeback=True): """LDM: 批量加载多个寄存器 从R0指向的地址开始,连续读取reg_list中每个寄存器""" addr = self.regs[rn] for reg in reg_list: self.regs[reg] = self.memory.get(addr, 0) addr += 4 if writeback: self.regs[rn] = addr return self.regs[rn] def stm(self, rn, reg_list, writeback=True): """STM: 批量存储多个寄存器 将reg_list中每个寄存器的值连续写入内存""" addr = self.regs[rn] for reg in reg_list: self.memory[addr] = self.regs[reg] addr += 4 if writeback: self.regs[rn] = addr return self.regs[rn] def main(): sim = ARMSimulator() # 初始化内存:地址 100 处放值 42 sim.memory[100] = 42 sim.memory[104] = 7 sim.memory[108] = 99 # 1. 基本 LDR:R1 = 100,LDR R0, [R1] sim.regs[1] = 100 val = sim.ldr(0, 1) print("LDR R0, [R1] -> R0 =", val) # 2. STR:R0 = 42,STR R0, [R1] sim.regs[0] = 42 sim.regs[1] = 200 addr = sim.str(0, 1) print("STR R0, [R1] -> 内存[", addr, "] =", sim.memory[200]) # 3. 前变址:LDR R0, [R1, #4]! sim.regs[1] = 100 val = sim.ldr(0, 1, offset=4, pre=True, writeback=True) print("LDR R0, [R1, #4]! -> R0 =", val, ", R1 =", sim.regs[1]) # 4. 后变址:LDR R0, [R1], #4 sim.regs[1] = 100 val = sim.ldr(0, 1, offset=4, pre=False) print("LDR R0, [R1], #4 -> R0 =", val, ", R1 =", sim.regs[1]) # 5. STM 批量存储 + LDM 批量加载 sim.regs[0] = 10 sim.regs[1] = 20 sim.regs[2] = 30 base_addr = 300 sim.regs[3] = base_addr sim.stm(3, [0, 1, 2]) print("STM R3!, {R0-R2} -> 内存[300]=10, [304]=20, [308]=30") sim.regs[3] = base_addr sim.regs[0] = sim.regs[1] = sim.regs[2] = 0 sim.ldm(3, [0, 1, 2]) print("LDM R3!, {R0-R2} -> R0 =", sim.regs[0], ", R1 =", sim.regs[1], ", R2 =", sim.regs[2]) if __name__ == "__main__": main()
class ARMSimulator:建立指令模拟器类,内存用字典、寄存器用列表,相当于图书馆的管理系统。def ldr(self, rd, rn, offset=0, pre=False, writeback=False):模拟 LDR 指令,pre=True时先算地址,反之先访问再更新基址。addr = self.regs[rn] + offset if pre else self.regs[rn]前变址用“基址+偏移”作地址,后变址直接用当前基址。self.regs[rd] = self.memory.get(addr, 0)从内存取数据存入目标寄存器,未初始化地址按 0 处理。def stm(self, rn, reg_list, writeback=True):批量存储,把多个寄存器依次写入连续内存。sim.ldr(0, 1, offset=4, pre=True, writeback=True)演示前变址回写:加载 R1+4 处数据后,R1 自动更新为 R1+4。sim.ldr(0, 1, offset=4, pre=False)演示后变址:先取 R1 处数据,再让 R1 加 4。
常见误区
误区我以为 LDR 和 STR 都是双向的,可以互相替换使用,反正都是内存和寄存器之间传数据。写代码时经常随手互换,出错了也不知道为什么。
正解LDR 的方向永远是“内存 → 寄存器”(取书到工作台),STR 的方向永远是“寄存器 → 内存”(把书放回书架)。单向性是硬件设计决定的,指令编码中已经写死了方向。你不可能让 LDR 把寄存器数据写到内存,就像你不可能让“取书”动作变成“放书”。
为何常见:我们平时编程用高级语言,赋值语句 a = b 看起来是双向的“把 b 给 a”,容易形成“数据搬运是双向”的直觉。但汇编指令是硬件的直接映射,方向是绝对的单向。
误区前变址和后变址的区别就是有没有感叹号,写的时候经常搞混。我以为加了感叹号就是后变址,不加就是前变址。
正解前变址([R1, #4])是“先算新地址再访问”,后变址([R1], #4)是“先访问再算新地址”。感叹号只表示“是否回写基址寄存器”,而非决定前/后变址。判断方法很简单:偏移量写在方括号内就是前变址,写在方括号外就是后变址。
为何常见:感叹号像“语法装饰品”,容易让人误以为是模式开关。实际上前变址也可以不带感叹号(访问但不回写),后变址的基址更新是强制的。关键在于偏移量的书写位置而非感叹号。
误区我认为 `LDM R0!, {R1-R4}` 和 `LDM R0!, {R4-R1}` 效果一样,反正都是把 R1 到 R4 四个寄存器加载一遍。
正解ARM 规定 LDM/STM 寄存器列表中的编号是按从低到高依次处理的,编号最低的寄存器对应最低的内存地址。写 {R4-R1} 并不会“倒序加载”,ARM 汇编器通常要求列表按升序书写,而且某些汇编器直接禁止倒序写法。这与栈操作中“后进先出”的顺序配合时非常容易出错。
为何常见:数学直觉告诉我们 {1,2,3,4} 和 {4,3,2,1} 的元素相同,但在汇编指令中,集合的书写顺序直接影响硬件行为——先处理编号小的寄存器。
误区既然新处理器支持非对齐访问,那我可以放心地在所有代码里使用非对齐地址,不用管什么对齐不对齐。
正解虽然较新的 ARM 处理器(ARMv7 及以上)支持非对齐访问,但 LDM、STM、LDRD、STRD 等批量指令仍然要求严格对齐。非对齐的 LDM 会直接引发异常。此外,某些 DMA 控制器和外设也要求对齐。代码在支持非对齐的 CPU 上正常,不代表换到别的平台也正常。
为何常见:理论课讲“处理器支持非对齐访问”时没强调例外情况,学习者容易把“部分支持”理解成“全部支持”。
误区`LDR R0, [R1, #4]` 我以为是从 R1 指向的位置往后数 4 个元素(如果数组是 int 类型,就是 4 个 int),但实际上……
正解ARM 汇编中偏移量的单位是字节,不是元素个数。如果 R1 指向一个 int 数组,想取下一个 int 元素,偏移量应写成 #4(4 字节),而不是 #1。这与 C 语言中 ptr + 1 自动按元素大小偏移的语义完全不同——汇编是裸的硬件视角,一切按字节算。
为何常见:C 语言的指针算术具有“类型感知”能力,p + 1 自动跳过一个元素。学习者从 C 转到汇编时,容易把这种类型感知的偏移习惯带过来。
面试题
Q1. 在 ARM 汇编中,LDR R0, [R1] 指令的含义是什么?
- A.将 R0 的值存储到 R1 指向的内存
- B.将 R1 指向的内存数据加载到 R0← 正确答案
- C.将 R0 和 R1 的值相加
- D.将 R1 的值存储到 R0 指向的内存
答案:B。LDR 是加载指令,方向是“内存 → 寄存器”。R1 提供内存地址,该地址处的数据被读入 R0,好比从书架上取书放到工作台。A 是 STR 的语义,C 是 ADD,D 方向完全反了。
Q2. 关于 STR 指令,下列说法正确的是?
- A.STR 将内存数据加载到寄存器
- B.STR 将寄存器数据存储到内存← 正确答案
- C.STR 可以同时双向传输数据
- D.STR 与 LDR 功能完全相同
答案:B。STR 的方向是“寄存器 → 内存”,把寄存器中的值写入指定地址,相当于把书从工作台放回书架。A 是 LDR 的功能,C 和 D 都错误,因为 LDR 和 STR 都是单向的且功能不同。
Q3. 指令 LDR R0, [R1, #4]! 执行后,R1 的值会怎样变化?
- A.R1 保持不变
- B.R1 更新为 R1 + 4← 正确答案
- C.R1 更新为 R0 的值
- D.R1 清零
答案:B。这是前变址回写模式。[R1, #4]! 先用 R1+4 作为有效地址访问,然后因为带有感叹号,R1 会被回写为新地址 R1+4,相当于“先算出新位置再更新索书单”。
Q4. 指令 LDR R0, [R1], #4 执行后,下列说法正确的是?
- A.先访问 R1+4 地址,然后 R1 不变
- B.先访问 R1+4 地址,然后 R1 更新为 R1+4
- C.先访问 R1 地址,然后 R1 更新为 R1+4← 正确答案
- D.先访问 R1 地址,然后 R1 不变
答案:C。这是后变址模式,偏移量在方括号外。先以 R1 当前值为地址访问内存,访问结束后 R1 自动加上偏移量 4。后变址的基址更新是强制的,不需要感叹号,好比“先按旧索书单取书,再更新索书单”。
Q5. 以下哪些指令属于 ARM 的批量数据传输指令?
- A.LDMIA← 正确答案
- B.STMDB← 正确答案
- C.LDRH
- D.STRB
答案:AB。LDM(Load Multiple)和 STM(Store Multiple)系列是批量指令,一次操作多个寄存器。LDMIA 和 STMDB 都是批量指令。LDRH 是半字加载、STRB 是字节存储,都是单寄存器指令。
Q6. ARM 中“字”(Word)的大小是多少字节?
- A.1 字节
- B.2 字节
- C.4 字节← 正确答案
- D.8 字节
答案:C。ARM 架构中一个字是 32 位,即 4 字节。字对齐要求地址是 4 的倍数,半字(Halfword)是 2 字节,要求地址是 2 的倍数。这解释了为什么访问 int 数组元素时偏移量要加 4。
Q7. 在 ARMv7 架构中,以下哪种情况一定会引发对齐异常?
- A.LDR 访问非对齐地址
- B.LDRH 访问奇数地址
- C.LDM 访问非对齐地址← 正确答案
- D.STRB 访问任意地址
答案:C。LDM/STM 等批量指令要求严格对齐,地址非对齐时必定引发异常。普通 LDR 在支持非对齐访问的处理器上可以正常执行(由硬件拆分),LDRH 要求地址为 2 的倍数,访问奇数地址也可能异常。STRB 只写一个字节,不存在对齐问题。
Q8. 关于 LDM/STM 指令的作用,下列说法正确的有?
- A.一次操作可以加载或存储多个寄存器← 正确答案
- B.常用于中断处理中的现场保护← 正确答案
- C.只能操作 R0-R3 四个寄存器
- D.访问的内存地址必须是连续的← 正确答案
答案:ABD。LDM/STM 从连续内存地址存取多个寄存器(A、D 正确),寄存器列表可以是 R0-R15 的任意组合。现场保护是典型应用(B 正确)。C 错误,寄存器列表不限于 R0-R3。
Q9. 执行 STMDB SP!, {R0-R3} 指令后,SP 的值如何变化?(假设 SP 初始指向地址 100)
- A.SP 变为 100
- B.SP 变为 116
- C.SP 变为 84← 正确答案
- D.SP 变为 96
答案:C。STMDB 是“Decrement Before“模式——先减地址再存储。4 个寄存器共 16 字节,SP 先减去 16 得到 84,然后从 84 开始依次存储 R0 到 R3,最终 SP 停在 84。这就是栈操作中”向下增长”的压栈行为。
Q10. 在 C 语言中访问 int arr[10],若 arr 的起始地址在 R0 中,要读取 arr[2],应该使用哪条指令?
- A.LDR R1, [R0, #2]
- B.LDR R1, [R0, #8]← 正确答案
- C.LDR R1, [R0, #4]
- D.LDR R1, [R0], #2
答案:B。int 在 ARM 中是 4 字节,arr[2] 距起始地址偏移 2×4=8 字节。前变址格式 [R0, #8] 计算 R0+8 作为有效地址,加载该处的值到 R1,相当于“跳过 2 个 int 再取第 3 个”。A 只偏移 2 字节,C 是 arr[1],D 是后变址且偏移量错误。