公众号:嵌入式不难
本文仅供参考学习,如有错误之处,欢迎留言指正。
文章目录
一、官方原始代码及内存分配
1. startup_stm32f103xb.s文件中,内存分配的原始汇编代码
```
; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
;EQU汇编指令用于为程序中的常量、标号等定义一个等效的字符名称,类似c中#define
; 此指令编译器并不会为次字符分配内存,仅仅是为了便于阅读
;AREA 段名 属性1 ,属性2 ,…… 用于定义一个代码段或数据段
; STACK为段名
; NOINIT指定此数据段仅仅保留了内存单元(初始为0)
; READWRITE指定本段可读可写
; ALIGN=3指定该段地址按2^3字节字节对齐
;SPACE用于分配一片连续的存储单元(字节为单位)。
; SPACE指令前的LABEL即此例中的Stack_Mem表示此段的起始地址
;__initial_sp 以下基于我自己的理解,事实证明我的理解也是很正确的
; 首先标号可能一个字符,也可能是一个地址,标号必须顶格写
; 它用于表示一个字符时,即是EQU前的标号用法
; 它用于表示一个地址时,类似于c中的指针,即是SPACE前的标号的用法
; 现在Stack_Mem起始地址已经分配了Stack_Size大小空间了,然后这一段空间已经被使用了,
; 所以下一个标号地址一定是紧接着上一个被使用的地址后的地址,即是__initial_sp=Stack_Mem+Stack_Size
Stack_Size EQU 0x400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
```
2.编译生成的.map文件截取
1).截取部分.map文件内容
HEAP 0x20000030 Section 512 startup_stm32f103xb.o(HEAP)
STACK 0x20000230 Section 1024 startup_stm32f103xb.o(STACK)
__heap_base 0x20000030 Data 0 startup_stm32f103xb.o(HEAP)
__heap_limit 0x20000230 Data 0 startup_stm32f103xb.o(HEAP)
__initial_sp 0x20000630 Data 0 startup_stm32f103xb.o(STACK)
2).解析
由上述startup_stm32f103xb.s汇编文件可知
HEAP --> 表示堆的数据段名,指向了堆的起始地址
STACK --> 表示栈的数据段名,指向了栈的起始地址
__heap_base --> 指向了堆的起始地址
__heap_limit --> 指向了堆的结束地址
__initial_sp --> 指向了栈的起始地址
注意:
①.这里指的起始地址和结束地址均表示地址由小到大排列,即是小地址为起始地址,大地址为结束地址
②.堆增长方向:向上生长
③.栈增长方向:向下生长
二、疑点
1. 基础疑点
- 1.1. 为什么.map文件中并没有HEAP/STACK/__heap_base/__heap_limit/__initial_sp定义
- 1.2. 为什么.map文件中只有STACK和__initial_sp,没有HEAP/__heap_base/__heap_limit的定义
2. 增强疑点
- 1.1. 为什么.s文件中 AREA STACK, NOINIT, READWRITE, ALIGN=3 和Stack_Mem SPACE Stack_Size的代码行数明明在AREA HEAP, NOINIT, READWRITE, ALIGN=3和Heap_Mem SPACE Heap_Size的代码行数前定义,结果.map文件中__initial_sp(栈结束地址)还要比__heap_limit(堆结束地址)大呢?难道不是定义在前面的区域就先分配吗?
三、解疑方法(实验方法)
1. 基础疑点解答
- 首先确定Options中Listing栏Linker Listing 下方所有的选项保持选中
- 1.1. 没有勾选Options中Target栏USE MicroLIB项
- 1.2. 因为程序中并没有使用堆空间,编译器自动优化不适用堆空间,需用函数malloc使用堆空间
2. 增强疑点解答
- 实验方案1:可先调换申请堆空间和栈空间的申请代码,观察.map文件是否有变化
-
实验方案1步骤1:调换申请堆空间和栈空间的申请代码,结果如下
; <h> Heap Configuration ; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8> ; </h> Heap_Size EQU 0x200 AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base Heap_Mem SPACE Heap_Size __heap_limit ; Amount of memory (in bytes) allocated for Stack ; Tailor this value to your application needs ; <h> Stack Configuration ; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8> ; </h> Stack_Size EQU 0x400 AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE Stack_Size __initial_sp
-
实验方案1步骤2:编译后观察.map文件地址,结果如下
__heap_base 0x20000030 Data 0 startup_stm32f103xb.o(HEAP) __heap_limit 0x20000230 Data 0 startup_stm32f103xb.o(HEAP) __initial_sp 0x20000630 Data 0 startup_stm32f103xb.o(STACK)
-
实验方案1分析:__heap_base、__heap_limit、__initial_sp与调换前的地址一致,说明调换代码前后顺序并不能改变编译结果,即调换代码位置并不能改变堆栈的地址
-
-
实验方案2:在实验方案1的结论下,先将.s文件的顺序还原至初始状态,在此基础上,再添加一段申请数据段内存的代码,观察申请的内存处于什么位置
- 实验方案2步骤1:添加一段申请内存的代码。结果如下
- 1)…修改.s文件片段1–>申请一个数据段
MY_Test_Size EQU 0x100 AREA MY_Test_Base, NOINIT, READWRITE, ALIGN=3 __MY_Test_Start MY_Test_Data SPACE MY_Test_Size __MY_Test_End ; Amount of memory (in bytes) allocated for Stack ; Tailor this value to your application needs ; <h> Stack Configuration ; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8> ; </h> Stack_Size EQU 0x400 AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE Stack_Size __initial_sp ; <h> Heap Configuration ; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8> ; </h> Heap_Size EQU 0x200 AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base Heap_Mem SPACE Heap_Size __heap_limit
- 2)…修改.s文件片段2–>外部声明变量
IF :DEF:__MICROLIB EXPORT __initial_sp EXPORT __heap_base EXPORT __heap_limit EXPORT __MY_Test_Start EXPORT __MY_Test_End ELSE
- 3)…修改main.c文件片段2–>外部引用变量,防止编译器优化
extern int *__MY_Test_Start; extern int *__MY_Test_End; temp_all = *__MY_Test_Start; temp_all = *__MY_Test_End;
- 1)…修改.s文件片段1–>申请一个数据段
- 实验方案2步骤2:编译后观察.map文件地址,结果如下
HEAP 0x20000030 Section 512 startup_stm32f103xb.o(HEAP) MY_Test_Base 0x20000230 Section 256 startup_stm32f103xb.o(MY_Test_Base) STACK 0x20000330 Section 1024 startup_stm32f103xb.o(STACK) __heap_base 0x20000030 Data 0 startup_stm32f103xb.o(HEAP) __MY_Test_Start 0x20000230 Data 0 startup_stm32f103xb.o(MY_Test_Base) __heap_limit 0x20000230 Data 0 startup_stm32f103xb.o(HEAP) __MY_Test_End 0x20000330 Data 0 startup_stm32f103xb.o(MY_Test_Base) __initial_sp 0x20000730 Data 0 startup_stm32f103xb.o(STACK)
- 实验方案2分析:由实验结果可知新申请的内存插在了堆栈之间,占据了0x20000230至0x20000330内存空间,就显得更加奇怪了,怎么会跑到这儿去了呢,通过我两天的测试,终于找到答案,首先需要分配一段内存肯定得通过AREA伪指令,然后AREA伪指令后面的段名和属性成了两个关键点,MDK-ARM编译器会根据属性来判断存储于什么位置,再根据段名首字母来判断先为谁分配内存,顺序表现为A开头段名最优先,Z开头段名排最后,由上述可知HEAP段应最先分配,MY_Test_Base段第二分配,STACK段最后分配,通过对比试验数据可得知此结论成立
- 实验方案2步骤1:添加一段申请内存的代码。结果如下
四、结论
- AREA伪指令后面的段名和属性是两个关键点,MDK-ARM编译器会根据属性来判断存储于什么位置,再根据段名首字母来判断先为谁分配内存,顺序表现为A开头段名最优先,Z开头段名排最后