7、ARM_栈

概述

什么是栈:

栈的本质就是一段内存,程序运行时用于保存一些临时数据,如局部变量、函数的参数、返回值、以及程序跳转时需要保护的寄存器等

什么是压栈和出栈:

压栈就是把数据写入栈,就是写内存。

出栈就是把栈中的数据读出来,就是读内存。

什么是增栈和减栈:

增栈和减栈是对于压栈过程中的地址偏移方向的分类,增栈就是新的数据向高地址存,减栈就是新的数据向低地址存。

在下图中,DATA1存放到SP后,下一个数据DATA2存在高地址位置,这就是增栈;下一个数据DATA2存在低地址位置,这就是减栈

增栈与减栈的压栈和出栈过程中的SP指针偏移关系如下:

栈的分类压栈出栈
增栈SP向高地址偏移SP向低地址偏移
减栈SP向低地址偏移SP向高地址偏移

什么是满栈和空栈:

满栈与空栈是对于压栈过程中SP指针指向的位置是否有数据的分类,满栈就是SP指向的空间中就是栈中的数据,空栈就是SP指向的空间是一个没有数据的空间。

在下图中,当DATA2想要压栈时,SP指向的位置是DATA1的位置,这就是满栈;SP指向的位置是DATA1的下一个地址的位置,这就是空栈。究竟下一个地址是高地址还是低地址,这与增栈和减栈有关。

满栈与空栈的压栈过程中SP移动的过程如下:

栈的分类压栈出栈
满栈SP先偏移,再压栈先出栈,再SP偏移
空栈先压栈,再SP偏移SP先偏移,再出栈

栈的分类总结:

栈的分类就是 "增栈与满栈" 和 "满栈与空栈" 这两者的结合:空增EA、空减ED、满增FA、满减FD

其中:E代表empty、F代表full、A代表ascendant、D代表descendant。ARM中常用FD的栈

栈的类型与压栈出栈(STM、LDM)指令的关系如下:

栈的分类压栈出栈
空增EASTMIALDMDB
空减EDSTMDALDMIB
满增FASTMIBLDMDA
满减FDSTMDBLDMIA

栈的类型与专门后缀的压栈出栈(STM、LDM)指令的关系如下:

栈的分类压栈出栈
空增EASTMEALDMEA
空减EDSTMEDLDMED
满增FASTMFALDMFA
满减FDSTMFDLDMFD

注意:这些EA、ED、FA、FD并不是新的指令,编译器会把他们编译为相应的指令。如:STMFD最终编译成汇编依旧是STMDB

栈的应用

1、叶子函数调用过程

什么是叶子函数:

叶子函数就是只有一层的函数,即:main中调用了fun函数,fun函数中没有调用其他的函数。

实现功能:

使用汇编实现函数调用,main中计算1+2并调用fun,fun中计算20-10。要求只能使用R1、R2、R3这三个寄存器。

设计思路:

  • 初始化栈指针SP
  • 入栈当前寄存器中的数据,即:压栈保护现场
  • 进行函数相关的操作
  • 出栈数据到寄存器中,即:出栈恢复现场
  • 移动PC到断点下一条指令位置

代码实现:

.text 
.global _start
_start:

MOV SP,#0x40000020 	   @初始化栈指针sp

MAIN:
	MOV R1,#1
	MOV R2,#2
	BL FUN
	ADD R3,R1,R2
	B STOP
	
FUN:
	STMFD SP!,{R1,R2}  @入栈保护现场
	MOV R1,#10
	MOV R2,#20
	SUB R3,R2,R1
	LDMFD SP!,{R1,R2}  @出栈恢复现场
	MOV PC,LR          @PC指向断点下一个指令位置

STOP:
	B STOP

.end

2、非叶子函数调用过程

什么是非叶子函数:

非叶子函数就是有多层的函数,即:main中调用了fun函数,fun函数中还调用其他的函数。

实现功能:

使用汇编实现函数调用,main中计算1+2并调用fun,fun中计算20-10并调用fun2,fun2中计算300-200。要求只能使用R1、R2、R3这三个寄存器。

设计思路:

  • 初始化栈指针SP
  • 入栈当前寄存器和LR中的数据,即:压栈保护现场
  • 进行函数相关的操作
  • 出栈数据到寄存器中,即:出栈恢复现场
  • 移动PC到断点下一条指令位置

代码实现:

.text 
.global _start
_start:

MOV SP,#0x40000020 	      @初始化栈指针sp

MAIN:
	MOV R1,#1
	MOV R2,#2
	BL FUN
	ADD R3,R1,R2
	B STOP
	
FUN:
	STMFD SP!,{R1,R2,LR}  @入栈保护现场,注意这里入栈了LR
	MOV R1,#10
	MOV R2,#20
	BL FUN2
	SUB R3,R2,R1
	LDMFD SP!,{R1,R2,LR}  @出栈恢复现场
	MOV PC,LR             @PC指向断点下一个指令位置

FUN2:
	STMFD SP!,{R1,R2,LR}  @入栈保护现场,注意这里入栈了LR
	MOV R1,#200
	MOV R2,#300
	SUB R3,R2,R1
	LDMFD SP!,{R1,R2,LR}  @出栈恢复现场
	MOV PC,LR             @PC指向断点下一个指令位置

STOP:
	B STOP

.end

3、局部变量是随机值的原因

出栈发生了什么:

以"叶子函数调用过程"中的代码为例,当程序走到STOP后,栈中的数据已经出栈,但是栈中保存的数据并未被清除。即:出栈只是SP去移动,而不是顺带着把栈中的数据清零

局部变量是随机值的原因:

当另一个函数被调用,局部变量被申请时,局部变量的地址会从栈指针位置去申请,这里就是申请0x000000001C这个空间,所以在不初始化的情况下,该局部变量的值就是2,是上一个函数入栈的数据,因此是一个随机值。

<think>嗯,用户想了解Arm_Lib的信息,包括介绍、使用方法和相关资料。之前提供了几个引用,需要从中提取关键点来解释Arm_Lib。 用户提到的引用[1]和[5]都提到ARM_LIB_HEAP和ARM_LIB_STACK,看起来是内存布局中的区域定义。引用[5]的分散加载描述文件详细说明了它们在内存中的位置:堆区从0x20100000开始向上增长,区从0x20200000开始向下增长。这些应该是ARM编译器中用于标准C库内存管理的保留区域。 引用[2][3][4]提到了编译错误"Undefined symbol Image$$ARM_LIB_STACK$$ZI$$Limit",解决方法都是修改链接脚本配置。用户需要知道这通常是由于链接脚本未正确定义堆区域导致的,解决方法是在MDK中选择正确的.sct文件或手动定义这些符号。 关于介绍部分,可以说明Arm_Lib是ARM编译器内置的运行时环境组件,管理堆等关键内存区域。使用方法需要强调在MDK中的配置步骤,特别是链接脚本的选择和内存区域的映射。资料方面,ARM官方文档和Keil MDK手册应该是最权威的参考。 用户可能实际遇到了编译错误,所以需要特别突出问题解决步骤。同时要提醒堆大小设置需根据具体应用调整,太小会导致运行时错误。还可以补充常见问题,比如为什么需要手动定义这些符号,毕竟这是ARM架构内存管理的特殊要求。 </think>以下基于ARM开发环境(Keil MDK/IAR等)对ARM_LIB相关概念的整理: --- ### **一、ARM_LIB介绍** `ARM_LIB` 是ARM编译器(ARMCC/ARMClang)用于**管理运行时内存布局**的关键符号,主要定义以下核心区域: 1. **ARM_LIB_HEAP** - 作用:标准C库动态内存分配(如`malloc`)的堆区。 - 特性:**向上增长**的内存块,需在链接脚本中显式声明大小。 - 示例定义(链接脚本): ``` ARM_LIB_HEAP 0x20000000 EMPTY 0x8000 {} ; 32KB堆空间 ``` 2. **ARM_LIB_STACK** - 作用:主空间,存放局部变量、函数调用上下文。 - 特性:**向下增长**,需独立定义且通常位于RAM末端。 - 示例定义: ``` ARM_LIB_STACK 0x20008000 EMPTY -0x4000 {} ; 16KB空间(从高地址向低地址扩展) ``` 3. **关键符号** 编译器依赖以下符号初始化堆(需链接脚本声明): - `Image$$ARM_LIB_STACK$$ZI$$Limit` → 顶地址 - `Image$$ARM_LIB_HEAP$$ZI$$Base` → 堆起始地址 --- ### **二、典型问题与解决方法** #### **问题:未定义符号错误(L6218E)** ```bash Error: L6218E: Undefined symbol Image$$ARM_LIB_STACK$$ZI$$Limit ``` **原因**:链接脚本未正确定义ARM_LIB区域或工程配置未加载对应分散加载文件(.sct)。 **解决步骤**(以Keil MDK为例): 1. 打开工程选项 → **Linker** 选项卡 2. 取消勾选 *"Use Memory Layout from Target Dialog"* 3. 在 *"Scatter File"* 中选择设备对应的.sct文件: - ARMCM3 → `ARMCM3_ac5.sct` (AC5编译器) - ARMCM4 → `ARMCM4_ac6.sct` (AC6编译器) > 路径通常为:`.\RTE\Device\<Device_Name>\`[^2][^3] --- ### **三、与RTT_HEAP的区别** - **ARM_LIB_HEAP** ARM标准库专属堆,需通过链接脚本静态分配。 - **RTT_HEAP** RT-Thread操作系统自定义的动态内存池(如`rt_malloc`),独立于ARM_LIB: ```c // RT-Thread内存初始化示例 void rt_system_heap_init(void *begin_addr, void *end_addr) { /* 独立管理的内存池 */ } ``` > 两者可共存,但需避免地址重叠[^1]。 --- ### **四、配置建议** 1. **链接脚本定制** 手动编辑.sct文件,精确控制堆位置(参考[^5]): ```scatter ARM_LIB_STACK 0x20200000 EMPTY -0x8000 {} ; 区:32KB向下增长 ARM_LIB_HEAP 0x20100000 EMPTY 0x10000 {} ; 堆区:64KB向上增长 ``` 2. **堆大小调整** - 溢出风险:中断嵌套层数 × 帧大小 + 局部变量 - 堆不足场景:频繁动态分配(建议 ≥ 8KB) --- ### **五、参考资料** 1. **[ARM编译器手册]** *ARM® Compiler Scatter File Guide* (文档编号:DEN0013D) 2. **[Keil MDK文档]** *Configuring Stack and Heap Memory* (路径:Help → µVision Help) 3. **实战调试技巧** 使用`__heap_base`/`__heap_limit`符号在调试器监视堆边界[^4]。 > 注:若需RTOS集成(如FreeRTOS),建议关闭ARM_LIB_HEAP,改用OS自带内存管理[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值