本博文分为3部分,分别为启动代码篇,C堆栈篇,和STM32堆栈和uCOS堆栈区别篇.
***********************************************①启动代码篇***********************************************
前年毕业时,去了意法半导体工作,所以结识了STM32,先后学习了STM32和uCOS,在后边的学习中突然有了一个疑惑,就是堆栈的问题,在STM32的启动文件里定义的堆栈其实是很小的,但是,我写的uCOS程序其实占用了很大的堆栈空间,超过了启动代码中定义的堆栈大小,我们首先来看一下STM32的启动代码,这里以startup_stm32f10x_hd.s为例进行说明:
参考:https://www.cnblogs.com/afeibfp/archive/2013/01/08/2850408.html
;先在RAM中分配系统使用的栈,RAM的起始地址为0x2000_0000
;然后在RAM中分配变量使用的堆
;然后在CODE区(flash)分配中断向量表,flash的起始地址为0x0800_0000,该中断向量表就从这个起始地址开始分配
;分配完成后,再定义和实现相应的中断函数,
;所有的中断函数全部带有[weak]特性,即弱定义,如果编译器发现在别处文件中定义了同名函数,在链接时用别处的地址进行链接。
;中断函数仅仅实现了Reset_Handler,其他要么是死循环,要么仅仅定义了函数名称
;STM32被设置为从内部FLASH启动时(这也是最常见的一种情况),当STM32遇到复位信号后,
;从0x0800_0000处取出栈顶地址存放于MSP寄存器,从0x0800_0004处取出复位中断服务入口地址放入PC寄存器,
;继而执行复位中断服务程序Reset_Handler,
;Reset_Handler仅仅执行了两个函数调用,一个是SystemInit,另一个__main,
;SystemInit定义在system_stm32f10x.c中,主要初始化了STM的时钟系统:HSI,HSE,LSI,LSE,PLL,SYSCLK,USBCLK,APECLK等等.
;__main函数由编译器生成,负责初始化栈、堆等,并在最后跳转到用户自定义的main()函数,来到C的世界。
Stack_Size EQU 0x00000400 ;//定义堆栈大小
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;//定义一个数据段 按8字节对齐 ;AREA 伪指令用于定义一个代码段或数据段 NOINIT:指定此数据段仅仅保留了内存单元,而没有将各初始值写入内存单元,或者将各个内存单元值初始化为0
Stack_Mem SPACE Stack_Size ;//保留Stack_Size大小的堆栈空间 分 配连续 Stack_Size 字节的存储单元并初始化为 0
__initial_sp ;//标号,代表堆栈顶部地址,后面有用
; // Heap Configuration
; // Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; //
Heap_Size EQU 0x00000200 ;//定义堆空间大小
AREA HEAP, NOINIT, READWRITE, ALIGN=3 ;//定义一个数据段,8字节对齐
__heap_base ;//标号,代表堆末底部地址,后面有用
Heap_Mem SPACE Heap_Size ;//保留Heap_Size的堆空间
__heap_limit ;//标号,代表堆界限地址,后面有用
;//PRESERVE8 指令指定当前文件保持堆栈八字节对齐。 它设置 PRES8 编译属性以通知链接器。
;//链接器检查要求堆栈八字节对齐的任何代码是否仅由保持堆栈八字节对齐的代码直接或间接地调用。
PRESERVE8 ;//指示编译器8字节对齐
THUMB ;//指示编译器以后的指令为THUMB指令
;中断向量表定义
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY ;//定义只读数据段,其实放在CODE区,位于0地址
EXPORT __Vectors ;//EXPORT:在程序中声明一个全局的标号__Vectors,该标号可在其他的文件中引用
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; //Top of Stack ;给__initial_sp分配4字节32位的地址0x0
DCD Reset_Handler ; //Reset Handler ; 给标号Reset Handler分配地址为0x00000004
DCD NMI_Handler ; //NMI Handler ; 给标号NMI Handler分配地址0x00000008
DCD HardFault_Handler ; //Hard Fault Handler
DCD MemManage_Handler ; //MPU Fault Handler
DCD BusFault_Handler ; //Bus Fault Handler
DCD UsageFault_Handler ; //Usage Fault Handler
DCD 0 ; //Reserved ; 这种形式就是保留地址,不给任何标号分配
DCD 0 ; //Reserved
DCD 0 ; //Reserved
DCD 0 ; //Reserved
DCD SVC_Handler ; //SVCall Handler
DCD DebugMon_Handler ; //Debug Monitor Handler
DCD 0 ; //Reserved
DCD PendSV_Handler ; //PendSV Handler
DCD SysTick_Handler ; //SysTick Handler
; External Interrupts
DCD WWDG_IRQHandler ; //Window Watchdog
DCD PVD_IRQHandler ; //PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; //Tamper
DCD RTC_IRQHandler ; //RTC
DCD FLASH_IRQHandler ; //Flash
DCD RCC_IRQHandler ; //RCC
DCD EXTI0_IRQHandler ; //EXTI Line 0
DCD EXTI1_IRQHandler ; //EXTI Line 1
DCD EXTI2_IRQHandler ; //EXTI Line 2
DCD EXTI3_IRQHandler ; //EXTI Line 3
DCD EXTI4_IRQHandler ; //EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; //DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; //DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; //DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; //DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; //DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; //DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; //DMA1 Channel 7
DCD ADC1_2_IRQHandler ; //ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; //USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; //USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; //CAN1 RX1
DCD CAN1_SCE_IRQHandler ; //CAN1 SCE
DCD EXTI9_5_IRQHandler ; //EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; //TIM1 Break
DCD TIM1_UP_IRQHandler ; //TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; //TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; //TIM1 Capture Compare
DCD TIM2_IRQHandler ; //TIM2
DCD TIM3_IRQHandler ; //TIM3
DCD TIM4_IRQHandler ; //TIM4
DCD I2C1_EV_IRQHandler ; //I2C1 Event
DCD I2C1_ER_IRQHandler ; //I2C1 Error
DCD I2C2_EV_IRQHandler ; //I2C2 Event
DCD I2C2_ER_IRQHandler ; //I2C2 Error
DCD SPI1_IRQHandler ; //SPI1
DCD SPI2_IRQHandler ; //SPI2
DCD USART1_IRQHandler ; //USART1
DCD USART2_IRQHandler ; //USART2
DCD USART3_IRQHandler ; //USART3
DCD EXTI15_10_IRQHandler ; //EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; //RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; //USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; //TIM8 Break
DCD TIM8_UP_IRQHandler ; //TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; //TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; //TIM8 Capture Compare
DCD ADC3_IRQHandler ; //ADC3
DCD FSMC_IRQHandler ; //FSMC
DCD SDIO_IRQHandler ; //SDIO
DCD TIM5_IRQHandler ; //TIM5
DCD SPI3_IRQHandler ; //SPI3
DCD UART4_IRQHandler ; //UART4
DCD UART5_IRQHandler ; //UART5
DCD TIM6_IRQHandler ; //TIM6
DCD TIM7_IRQHandler ; //TIM7
DCD DMA2_Channel1_IRQHandler ; //DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; //DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; //DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; //DMA2 Channel4 & Channel5
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY ;//代码段定义
;// Reset Handler
;//利用PROC、ENDP这一对伪指令把程序段分为若干个过程,使程序的结构加清晰
Reset_Handler PROC ;//过程的开始
EXPORT Reset_Handler [WEAK];//[WEAK] 弱定义,意思是如果在别处也定义该标号(函数),在链接时用别处的地址。
;//如果没有其它定方定义,编译器也不报错,以此处地址进行链接。
;//EXPORT提示编译器该标号可以为外部文件引用。
IMPORT __main ;//通知编译器要使用的标号在其他文件
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0 ;//BX是ARM指令集和THUMB指令集之间程序的跳转
LDR R0, =__main ;//使用“=”表示LDR目前是伪指令不是标准指令。这里是把__main的地址给RO。
BX R0 ;//BX是ARM指令集和THUMB指令集之间程序的跳转
ENDP ;//过程的结束
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B . ;//原地跳转(即无限循环),
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
MemManage_Handler\
PROC
EXPORT MemManage_Handler [WEAK]
B .
ENDP
BusFault_Handler\
PROC
EXPORT BusFault_Handler [WEAK]
B .
ENDP
UsageFault_Handler\
PROC
EXPORT UsageFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_Handler [WEAK]
B .
ENDP
DebugMon_Handler\
PROC
EXPORT DebugMon_Handler [WEAK]
B .
ENDP
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
Default_Handler PROC
EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
EXPORT TAMPER_IRQHandler [WEAK]
EXPORT RTC_IRQHandler [WEAK]
EXPORT FLASH_IRQHandler [WEAK]
EXPORT RCC_IRQHandler [WEAK]
EXPORT EXTI0_IRQHandler [WEAK]
EXPORT EXTI1_IRQHandler [WEAK]
EXPORT EXTI2_IRQHandler [WEAK]
EXPORT EXTI3_IRQHandler [WEAK]
EXPORT EXTI4_IRQHandler [WEAK]
EXPORT DMA1_Channel1_IRQHandler [WEAK]
EXPORT DMA1_Channel2_IRQHandler [WEAK]
EXPORT DMA1_Channel3_IRQHandler [WEAK]
EXPORT DMA1_Channel4_IRQHandler [WEAK]
EXPORT DMA1_Channel5_IRQHandler [WEAK]
EXPORT DMA1_Channel6_IRQHandler [WEAK]
EXPORT DMA1_Channel7_IRQHandler [WEAK]
EXPORT ADC1_2_IRQHandler [WEAK]
EXPORT USB_HP_CAN1_TX_IRQHandler [WEAK]
EXPORT USB_LP_CAN1_RX0_IRQHandler [WEAK]
EXPORT CAN1_RX1_IRQHandler [WEAK]
EXPORT CAN1_SCE_IRQHandler [WEAK]
EXPORT EXTI9_5_IRQHandler [WEAK]
EXPORT TIM1_BRK_IRQHandler [WEAK]
EXPORT TIM1_UP_IRQHandler [WEAK]
EXPORT TIM1_TRG_COM_IRQHandler [WEAK]
EXPORT TIM1_CC_IRQHandler [WEAK]
EXPORT TIM2_IRQHandler [WEAK]
EXPORT TIM3_IRQHandler [WEAK]
EXPORT TIM4_IRQHandler [WEAK]
EXPORT I2C1_EV_IRQHandler [WEAK]
EXPORT I2C1_ER_IRQHandler [WEAK]
EXPORT I2C2_EV_IRQHandler [WEAK]
EXPORT I2C2_ER_IRQHandler [WEAK]
EXPORT SPI1_IRQHandler [WEAK]
EXPORT SPI2_IRQHandler [WEAK]
EXPORT USART1_IRQHandler [WEAK]
EXPORT USART2_IRQHandler [WEAK]
EXPORT USART3_IRQHandler [WEAK]
EXPORT EXTI15_10_IRQHandler [WEAK]
EXPORT RTCAlarm_IRQHandler [WEAK]
EXPORT USBWakeUp_IRQHandler [WEAK]
EXPORT TIM8_BRK_IRQHandler [WEAK]
EXPORT TIM8_UP_IRQHandler [WEAK]
EXPORT TIM8_TRG_COM_IRQHandler [WEAK]
EXPORT TIM8_CC_IRQHandler [WEAK]
EXPORT ADC3_IRQHandler [WEAK]
EXPORT FSMC_IRQHandler [WEAK]
EXPORT SDIO_IRQHandler [WEAK]
EXPORT TIM5_IRQHandler [WEAK]
EXPORT SPI3_IRQHandler [WEAK]
EXPORT UART4_IRQHandler [WEAK]
EXPORT UART5_IRQHandler [WEAK]
EXPORT TIM6_IRQHandler [WEAK]
EXPORT TIM7_IRQHandler [WEAK]
EXPORT DMA2_Channel1_IRQHandler [WEAK]
EXPORT DMA2_Channel2_IRQHandler [WEAK]
EXPORT DMA2_Channel3_IRQHandler [WEAK]
EXPORT DMA2_Channel4_5_IRQHandler [WEAK]
WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Channel1_IRQHandler
DMA1_Channel2_IRQHandler
DMA1_Channel3_IRQHandler
DMA1_Channel4_IRQHandler
DMA1_Channel5_IRQHandler
DMA1_Channel6_IRQHandler
DMA1_Channel7_IRQHandler
ADC1_2_IRQHandler
USB_HP_CAN1_TX_IRQHandler
USB_LP_CAN1_RX0_IRQHandler
CAN1_RX1_IRQHandler
CAN1_SCE_IRQHandler
EXTI9_5_IRQHandler
TIM1_BRK_IRQHandler
TIM1_UP_IRQHandler
TIM1_TRG_COM_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
I2C1_EV_IRQHandler
I2C1_ER_IRQHandler
I2C2_EV_IRQHandler
I2C2_ER_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler
TIM8_BRK_IRQHandler
TIM8_UP_IRQHandler
TIM8_TRG_COM_IRQHandler
TIM8_CC_IRQHandler
ADC3_IRQHandler
FSMC_IRQHandler
SDIO_IRQHandler
TIM5_IRQHandler
SPI3_IRQHandler
UART4_IRQHandler
UART5_IRQHandler
TIM6_IRQHandler
TIM7_IRQHandler
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
B .
ENDP
ALIGN ;//填充字节使地址对齐
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
;堆和栈的初始化
IF :DEF:__MICROLIB ;//“DEF”的用法——:DEF:X 就是说X定义了则为真,否则为假
;如果定义了MICORLIB,
EXPORT __initial_sp ;//则将栈顶地址,
EXPORT __heap_base ;//堆起始地址赋予全局属性,
EXPORT __heap_limit ;//堆末端界限地址赋予全局属性,使外部程序可调用
ELSE ;//如果没定义__MICROLIB,则使用默认的C运行时库
IMPORT __use_two_region_memory ;;//通知编译器要使用的标号在其他文件__use_two_region_memory
EXPORT __user_initial_stackheap ;//声明全局标号__user_initial_stackheap,这样外程序也可调用此标号
;则进行堆栈和堆的赋值,在__main函数执行过程中调用
;如果使用默认的C库,程序启动过程中就不会执行该标号下的代码
__user_initial_stackheap ;//标号__user_initial_stackheap,表示用户堆栈初始化程序入口
;//则进行堆栈和堆的赋值,在__main函数执行过程中调用。
LDR R0, = Heap_Mem ;//保存堆始地址
LDR R1, =(Stack_Mem + Stack_Size) ;//保存栈的大小
LDR R2, = (Heap_Mem + Heap_Size) ;//保存堆的大小
LDR R3, = Stack_Mem ;//保存栈顶指针
BX LR
ALIGN ;//填充字节使地址对齐
ENDIF
END
其中堆栈是本篇的主题,我们来看堆栈的情况:
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; Heap Configuration
; Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
从上面看出栈大小为0x400,也就是1K byte,堆大小为0x200,也就是512 byte.这么点堆栈空间,如果写个uCOS程序,加入堆栈是从这里申请,那么很容易就会产生溢出情况.
但是,写uCOS时,如果uCOS的任务堆栈定义的其实很多,然后编译出来的hex运行也是完全没问题,于是就产生了疑惑,通过网上各路大神的答案中,终于知道了原因,在说明这个答案之前,先来普及一下C语言的堆栈概念,哈哈,还是有必要罗嗦一下:
***********************************************②C堆栈篇***********************************************
参考:http://blog.csdn.net/cc214042/article/details/52728924
1).堆栈的定义
栈(Stack):它是一种具有后进先出性质的数据结构,也就是说后存放的先取,先存放的后取。类似于数据结构中的栈(LIFO),但是它俩不是同一个对象,所以完全是两码事,一个是编译器级,一个是代码级.
堆(Heap):是一种经过排序的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指二叉堆。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。由于堆的这个特性,常用来实现优先队列,堆的存取是随意.注意这个不要和数据结构中的队列混为一谈.它俩完全不是同一个对象,所以概念不同.
2).内存的分配方式
内存分配方式有三种:
[1]从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
[2]在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。且不受程序员控制.
[3]从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块.
3).堆栈的申请和回收方式
不知道你是否有点明白了,堆和栈的第一个区别就是申请方式不同:栈(英文名称是stack)是系统自动分配空间的,例如我们定义一个 char a;系统会自动在栈上为其开辟空间。而堆(英文名称是heap)则是程序员根据需要自己申请的空间,例如malloc(10);开辟十个字节的空间。由于栈上的空间是自动分配自动回收的,所以栈上的数据的生存周期只是在函数的运行过程中,运行后就释放掉,不可以再访问。而堆上的数据只要程序员不释放空间,就一直可以访问到,不过缺点是一旦忘记释放会造成内存泄露。还有其他的一些区别我认为网上的朋友总结的不错这里转述一下:
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆。
结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的 delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
也就是说堆会在申请后还要做一些后续的工作这就会引出申请效率的问题。
4).C程序内存分配类别
一个由C/C++编译的程序占用的内存分为以下几个部分:
1、栈区(stack) 由编译器自动分配释放,存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址等。其操作方式类似于数据结构中的栈。
2、堆区(heap) 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
3、静态区(static)存放全局变量、静态数据、常量。程序结束后有系统释放
4、文字常量区常量字符串就是放在这里的。 程序结束后由系统释放。
5、程序代码区存放函数体(类成员函数和全局函数)的二进制代码。
****************************③STM32堆栈和uCOS堆栈区别篇*******************************
参考:http://www.openedv.com/posts/list/0/43106.htm
http://blog.csdn.net/qq_29119171/article/details/53764823
启动文件中的栈空间和堆空间和uCOS的任务堆栈空间不是同一回事,uCOS的任务堆栈空间是属于全局数据,存放在静态区,静态区和堆栈属于同一个级别,都是从RAM分配,而不是uCOS的堆栈从启动文件定义的堆栈分配.与此类似的还有uCOS的内存池等.
>>>>介于uCOS堆栈是全局数据,下面研究一下静态区和堆栈分配规则,我随便拿了一个以前写过的DMA测试代码,我们开分析一下编译后生成的map文件中截取的一部分内容:
.data 0x20000000 Section 20 stm32f10x_rcc.o(.data)
APBAHBPrescTable 0x20000000 Data 16 stm32f10x_rcc.o(.data)
ADCPrescTable 0x20000010 Data 4 stm32f10x_rcc.o(.data)
.data 0x20000014 Section 2 main.o(.data)
.bss 0x20000018 Section 5000 usart1.o(.bss)
.bss 0x200013a0 Section 96 libspace.o(.bss)
HEAP 0x20001400 Section 512 startup_stm32f10x_hd.o(HEAP)
Heap_Mem 0x20001400 Data 512 startup_stm32f10x_hd.o(HEAP)
STACK 0x20001600 Section 1024 startup_stm32f10x_hd.o(STACK)
Stack_Mem 0x20001600 Data 1024 startup_stm32f10x_hd.o(STACK)
__initial_sp 0x20001a00 Data 0 startup_stm32f10x_hd.o(STACK)
从上面的map数据来看,HEAP是堆定义的首地址(从低地址到高低之增长),它的分界线是STACK,栈的首地址是从__initial_sp处开始的(从高地址向低地址蔓延),它的分界线是STACK,可以看出如下关系:
HEAP: 0x20001400
STACK: 0x20001600
__initial_sp: 0x20001a00
STACK - HEAP == 0x200 == 512 byte;
__initial_sp - STACK == 0x400 == 1k byte;
这个数据正好符合启动文件定义的堆栈大小, 在STM32的datasheet上说,其RAM是从0x20000000开始的,从数据看出堆栈的起始地址是从0x20001400开始的,然后0x20000000~0x20001400之前存储的内容,我们可以从上面map文件的数据看出,存放了.data段,.bss段用了,.data段存储的是编译时已初始化的全局变量等,.bss段存放未初始化的全局变量等,先看一下.bss段:
0x20000018~0x200013a0这段存储的是usart1.o中的未初始化全局变量,大小是0x200013a0 - 0x20000018 == 5000 byte,我确实在usart1.c文件中定义了一个数组:uint8_t SendBuff[SENDBUFF_SIZE]; #define SENDBUFF_SIZE 5000
刚好吻合,然后地址0x200013a0~0x20001400这段存储的是库的全局数据(我用的是库版本写的代码),大小是0x20001400 - 0x200013a0 == 96 byte.
然后再看剩下的.data段,从0x20000000~0x20000014,共0x20000014 - 0x20000000 == 0x14 == 20 byte,并且分为了两段,一段是16 byte,一段是4 byte,通过查看stm32f10x_rcc.c文件,可以有定义两个初始化的静态全局变量:
static __I uint8_t APBAHBPrescTable[16] = {0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 4, 6, 7, 8, 9};
static __I uint8_t ADCPrescTable[4] = {2, 4, 6, 8};
正好是一个16 byte,一个4 byte符合map文件,剩下最后一个.data是定义在main.c函数中的全局变量,地址0x20000014这个,后边给出大小为2 byte,我确实在main.c函数中定义了一个全局的变量uint6_t i;但是我们有初始化,按说应该放在.bss段才对,我用的编译器是Keil,我猜,编译器给它优化时初始化了.以上就把map文件讲通了,也就是说全局等是定义在堆栈前面的区域的,而且定义的全局等的大小不同,将决定堆栈基地址的不同,但是烧录程序仅MCU后,是固定的,就按map显示的那种方式固定.如果程序中对堆栈处理不当,比如在栈中定义的数据已经超过了定义的大小1K byte时,就会去越界用堆的空间,这就很可怕了,搞不好是堆改变了越界栈的数据,也搞不好是越界栈改变堆中的数据,当然也许照样可以用,嘿嘿.就说这么多吧.