stm32堆栈的认知

flash和SRAM的区别
flash可以像硬盘一样存数据,也可以直接像RAM运行,一般在里面放bootload,程序执行代码。

1、stm32中的堆栈
单片机是一种集成电路芯片,集成cpu、ram、rom、多种i/o口和中断系统、定时器/计数器等功能。cpu中包括了各种总线电路,计算电路,逻辑电路,还有各种寄存器。stm32有通用寄存器R0-R15以及一些特殊功能寄存器,其中包括了堆栈指针寄存器。当stm32正常运行程序的时候,来了一个中断,CPU就需要将寄存器中的值压栈到ram里,然后将数据所在的地址存放在堆栈寄存器中。等中断处理完成退出时,再将数据出栈到之前的寄存器中,这个在c语言中是自动完成的。

2、编程中的堆栈
在编程中,很多时候会提到堆栈这个东西,准确的说这个就是ram中的一个区域。我们先了解几个说明:
(1)程序中的所有内容最终只会出现在flash,ram里。
(2)段的划分,是将类似数据种类存储在一个区域里,方便管理,正如上面所说,不管什么段的数据,最终都是在flash和ram里面。
c语言上分为堆、栈、bss、data、code段。具体每个段具体是存储什么数据,直接百度。接下来重点分析stm32以及在MDK里面段的划分。
MDK下code、ro-data,rw-data,zi-data着几个段:
code:存储程序代码的。
ro-data:存储const常量和指令。
rw-data:存储初始化值不为0的全局变量。
zi-data:存储未初始化的全局变量或初始化值为0的全局变量。

flash=code+RO Data + RW Data;
RAM=rw-data+zi-data;

这个是MDK编译之后能够得到的每个段的大小,也就能够得到占用响应的flash和ram大小,但是还是有两个数据段也会占用RAM,但是是在程序运行的时候,才会占用,那就是堆和栈。 在stm32的启动文件.s文件里面,就有堆栈的设置,其实这个堆栈的内存占用就是在上面RAM分配给rw-data+zi-data之后的地址开始分配的。

堆:是编译器调用动态内存分配的内存区域。
栈:是程序运行的时候局部变量的地方,所有局部变量用数组太大了都有可能造成栈溢出。

堆栈的大小在编译器编译之后是不知道的,只有运行的时候才知道,所以需要注意一点,就是别造成堆栈溢出了,,,不然就会有hardfault。

2、如果查看map文件

例子:
Total RO Size (Code + RO Data) 2980 ( 2.91kB)
Total RW Size (RW Data + ZI Data) 104 ( 0.10kB)
Total ROM Size (Code + RO Data + RW Data) 2988 (2.92kB)

Total ROM Size (Code + RO Data + RW Data)这样所写的程序占用的ROM的字节总数,也就是说程序所下载到rom flash中的大小为什么rom中还要存rw,因为掉电之后ram中所有数据都丢失了,每次上电ram的数据是被重新赋值的,每次这些固定的值就是存储在rom中,为什么不包含zi段呢,是因为自数据都是0
,没必要包含,只要程序运行之前将zi数据所在的区域一律清零即可,包含进去反而浪费存储空间。
实际上,rom中的指令至少应该有这样的功能:
(1)将rw从rom中搬到ram中,因为rw是变量,变量不能存在rom中。
(2)将zi所在的ram区域全部清零,因为zi区域并不在lmage中,所以需要程序根据编译器给出的zi地址以及大小来讲相应的ram区域清零,zi中也是变量,同理,变量不能存在rom中。

在程序运行的最初阶段,ro中的指令完成了这两项工作后c程序才能正常访问变量,否则只能运行不含变量的代码。

一、已经了解的

MDK编译之后的信息:
在这里插入图片描述
从中知道占用的flash=code+ro-data+rw-data

stm32 flash的初始地址是0x80000000,当然也可以自定义起始地址,不过记得在main函数中sing一变量后加一句SCB->VTOR==FLASH_BASE | OFFSET;其中offset是想要偏移的量,可宏定义或者直接0xXX。
当然也可以调用库函数NVIC_SetVectorTable()进行偏移,效果一样。
占用的SRAM=rw-data+zi-data。
stm32 sram的起始地址是0x20000000。看MDK的魔法棒设置target选项也是可以设置RAM起始地址和大小的,不过一般都不会动RAM的。

记忆小技巧:
前面3个是给flash,后面2个是给SRAM。
含义:
code:代码
ro-data:只读数据,字符串常量(经过const修饰的)
rw-data:可读可写数据,已经初始化的全局变量或者static修饰的变量(不管局部变量还是全局变量,没有显示的初始化的话,会初始化为0)
zi-data:没有进行初始化的全局变量。

二、基于map文件的分析
右边是.map文件放内容

const修饰的常量,不管在局部还是全局存放在flash 中,不占用RAM,所以为了节省RAM,把常量的字符串,数据等声明为const是推荐的一种做法
static修饰的变量,不管是在局部还是在全局存放于RAM 的.data段,就是已初始化变量区。static不显示初始化默认为初始化为0
初始化的全局变量存放在RAM 的.data段,就是已初始化变量区。static不显示初始化默认为初始化为0
未初始化的全局变量放在RAM的.bss段,也就是没有显示的进行初始化
HEAP 即堆区存放于RAM的HEAP段,堆区内存需要程序员用malloc和free进行动态分配和释放,注意不要丢失、改写了申请内存后得到的指针,以免释放不了,造成内存泄漏
STACK 即栈区存放于RAM 的STACK段,这部分是程序运行时函数局部变量使用的,使用完自动释放

在这里插入图片描述
PAD是“垫子、卫生巾”的意思。占用两个字节,姑且理解为”隔开各个段”吧。
宏定义、结构体、联合体、枚举类型等都属于代码。RAM值存放变量。但FLASH不止存放代码,还有变量,就是和RAM都有的交集,即MDK生成的RW-data(全局变量、static变量)。
疑问:这么多变量,它们是怎么排列在RAM中的呢?看上面的.map文件中的地址就知道了。就是一初始化的变量在前面,然后跟着是未初始化的全局变量,然后是堆,最后是栈。
也就是说栈和堆的起始地址是不可控的,是根据我们程序的全局变量多少计算出来的,一句话,吃剩下的。

三、裁剪栈和堆的大小
1、当我们程序运行到一个函数中莫名其妙的就崩了,进入HardFault_Handler,数组也没有越界啊?
可能的原因之一: 局部变量过多,栈溢出,不够用了,需要我们Tailor(裁剪)一下RAM。
ps: 数组越界也属于栈溢出。在启动文件中进行裁剪。HardFault_Handler在网上也有很多分析方法。

2、例如,如下启动文件我们可以把STACK_SIZE从0x400(1KB)改到0x800(2KB)一般就不会出现奔溃了,如果还崩,那要找找其他原因了(是不是函数使用了非法的指针,0xE0000000之类的MCU不认,不能访问的地址)等等。

3、当然有时候栈加上其他的,大小超过了RAM总大小,编译器就会提示不够,就要一点点加着试。

这时大小超过了RAM总大小,也能从一定程度说明程序设计存在不合理,几乎用光了RAM。比如全局变量设置的过大,世纪没用这么多,也设置这么多,一般多实际最大使用量加2个字节就没问题。大型的全局变量(即结构体全局变量、联合体全局变量)过多,重复繁杂,共用率不高。就要逐个文件删繁就简,砍掉能砍的全局变量, 缩小能缩的结构体,字节对齐,各种看家本领就要使用出来。如果砍到不能再砍的时候,只能砍功能或者换芯片了。

在这里插入图片描述

第三篇文章

1、查看:栈(stack)的问题
函数的局部变量,都是存放在“栈”里面,栈的英文是:STACK。STACK的大小我们可以在stm32的启动文件中设置,以战舰stm32开发板为例,在start_upstm32f10x_hd.s里面,开头就有:
stack_size EQU 0X00000800
表示栈大小是0x800,也就是2048字节,这样cpu处理任务的时候,函数局部变量最多可占用的大小就是2048字节。注意:是所有在处理的函数,包括函数嵌套、递归、等等,都是从“栈”里面分配的。
所有一个函数的局变量过多,比如在函数中定义一个u8 buf[512],这一下子就占用了1/4的栈大小了,再在其他函数里面来搞两下,程序奔溃是很容易的事了。这时候,一般就会进入hardfault…
这时初学者很容易犯的一个错误,切记不要在函数中放N多局部变量,尤其是有大数组的情况。
对于栈区,一般栈顶,也就是MSP,在程序刚运行的时候,指向程序所占用内存的最高地址,比如附件里面的这个程序,内存占用如图:

在这里插入图片描述
图中我们可以看到,程序总共占用内存:20+2348字节=2368=0x940
那么程序刚开始运行的时候:MSP=0x2000 0000+0x940=0x200000940。
事实上,也是如此,如图:
在这里插入图片描述
图中,MSP就是0x20000 0940。
程序运行后,MSP就是从这个地址开始,往下给函数的局部变量分配地址。

再说说栈的增长方向,我们可以用如下代码测试:

//保存栈增长方向
//0,向下增长;1,向上增长.
static u8 stack_dir;

//查找栈增长方向,结果保存在stack_dir里面.
void find_stack_direction(void)
{
    static u8 *addr=NULL; //用于存放第一个dummy的地址。
    u8 dummy;               //用于获取栈地址
    if(addr==NULL)    //第一次进入
    {                         
        addr=&dummy;     //保存dummy的地址
        find_stack_direction ();  //递归
    }
    else                //第二次进入
 	{  
        if(&dummy>addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的.
        else stack_dir=0;           //第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的. 
 	}
}

这个代码不是我写的,是从网上抄的,思路很巧妙,利用递归,判断两次分配给dummy的地址,来比较栈是向下增长还是向上增长。
如果你在stm32测试这个函数,你会发现,使stm32的栈,是向下增长的,事实上,一般CPU的栈增长方向,都是向下的。

2、再来说说,堆(HEAP)的问题

全局变量,静态变量,以及内存管理所用的内容,都是“堆”区,英文名:“HEAP”。
和栈区不同,堆区是从内存区域的起始地址,开始分配给各个全局变量和静态变量。
堆的生长方向,都是向上的。在程序里面,所有的内存分为:堆+栈。只是他们各自的起始地址和增长方向不同,他们没有 一个固定的界限,所以一旦堆栈冲突,系统就会奔溃。
我们测试一下:
在这里插入图片描述
sack_dir的地址是0x20000004,也就是stm32的内存起始端的地址。
这里本来应该是从0x20000000开始分配的,,但是仿真后发现,0x20000000总是存放:0x20000398,这个目前不清楚,等后续再解决。待解决
其他的,全局变量,则依次递增,地址肯定大于0x20000004,比如,cpu_endian的地址就是0x20000005,这时stm32内部堆的分配规则。
3、再说说,大小端的问题
大端模式:地位字节存在高地址上,高位字节存在低地址上。
小段模式:高位字节存在高地址上,低位字节存在低地址上。

stm32属于小端模式,简单的说,比如u32 temp=0x12345678;
假设temp地址在0x2000 0010。
那么在内存里面,存放就变成了:

地址hex
0x2000 001078 56 34 12

cpu到底是小端还是大端,可以通过如下代码测试:

//CPU大小端
//0,小端模式;1,大端模式.
static u8 cpu_endian;

//获取CPU大小端模式,结果保存在cpu_endian里面
void find_cpu_endian(void)
{
 	int x=1;
 	if(*(char*)&x==1)
 		cpu_endian=0; //小端模式
 	else cpu_endian=1;    //大端模式 
}

以上测试,在stm32上,你会得到cpu_endian=0,也就是小端模式。

3、最后说说,stm32内存的问题
在前面第一个图,程序总共占用内存:20+2348字节,那么多内存,到底是怎么得来的呢?
我们直接双击“工程名字”就能弹出map文件,在这里面,我们就可以很清楚的知道这些内存到底是怎么来的,在map文件最后,image部分有:

Image component sizes

      Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Object Name
       172         10          0          4          0        995   delay.o//delay.c里面,fac_us和fac_ms,共占用4字节
       112         12          0          0          0        427   led.o
        72         26        304          0       2048        828   startup_stm32f10x_hd.o  //启动文件,里面定义了Stack_Size为0X800,所以这里是2048.
       712         52          0          0          0       2715   sys.o
       348        154          0          6          0     208720   test.o//test.c里面,stack_dir和cpu_endian 以及*addr  ,占用6字节.
       384         24          0          8        200       3050   usart.o//usart.c定义了一个串口接收数组buffer,占用200字节.
    ----------------------------------------------------------------------
      1800        278        336         20       2248     216735   Object Totals //总共2248+20字节
         0          0         32          0          0          0   (incl. Generated)
         0          0          0          2          0          0   (incl. Padding)//2字节用于对其
    ----------------------------------------------------------------------
      Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Library Member Name
         8          0          0          0          0         68   __main.o
       104          0          0          0          0         84   __printf.o
        52          8          0          0          0          0   __scatter.o
        26          0          0          0          0          0   __scatter_copy.o
        28          0          0          0          0          0   __scatter_zi.o
        48          6          0          0          0         96   _printf_char_common.o
        36          4          0          0          0         80   _printf_char_file.o
        92          4         40          0          0         88   _printf_hex_int.o
       184          0          0          0          0         88   _printf_intcommon.o
         0          0          0          0          0          0   _printf_percent.o
         4          0          0          0          0          0   _printf_percent_end.o
         6          0          0          0          0          0   _printf_x.o
        12          0          0          0          0         72   exit.o
         8          0          0          0          0         68   ferror.o
         6          0          0          0          0        152   heapauxi.o
         2          0          0          0          0          0   libinit.o
         2          0          0          0          0          0   libinit2.o
         2          0          0          0          0          0   libshutdown.o
         2          0          0          0          0          0   libshutdown2.o
         8          4          0          0         96         68   libspace.o          //库文件(printf使用),占用了96字节
        24          4          0          0          0         84   noretval__2printf.o
         0          0          0          0          0          0   rtentry.o
        12          0          0          0          0          0   rtentry2.o
         6          0          0          0          0          0   rtentry4.o
         2          0          0          0          0          0   rtexit.o
        10          0          0          0          0          0   rtexit2.o
        74          0          0          0          0         80   sys_stackheap_outer.o
         2          0          0          0          0         68   use_no_semi.o
         2          0          0          0          0         68   use_no_semi_2.o
       450          8          0          0          0        236   faddsub_clz.o
       388         76          0          0          0         96   fdiv.o
        62          4          0          0          0         84   ffixu.o
        38          0          0          0          0         68   fflt_clz.o
       258          4          0          0          0         84   fmul.o
       140          4          0          0          0         84   fnaninf.o
        10          0          0          0          0         68   fretinf.o
         0          0          0          0          0          0   usenofp.o
    ----------------------------------------------------------------------
      2118        126         42          0        100       1884   Library Totals  //调用的库用了100字节.
        10          0          2          0          4          0   (incl. Padding)   //用于对其多占用了4个字节
    ----------------------------------------------------------------------
      Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Library Name
       762         30         40          0         96       1164   c_w.l
      1346         96          0          0          0        720   fz_ws.l
    ----------------------------------------------------------------------
      2118        126         42          0        100       1884   Library Totals
    ----------------------------------------------------------------------
==============================================================================

      Code (inc. data)   RO Data    RW Data    ZI Data      Debug  
      3918        404        378         20       2348     217111   Grand Totals
      3918        404        378         20       2348     217111   ELF Image Totals
      3918        404        378         20          0          0   ROM Totals
==============================================================================
    Total RO  Size (Code + RO Data)                 4296 (   4.20kB)
    Total RW  Size (RW Data + ZI Data)              2368 (   2.31kB)   //总共占用:2248+20+100=2368.
    Total ROM Size (Code + RO Data + RW Data)       4316 (   4.21kB)

通过这个文件,我们就可以分析整个内存,是怎么被占用的,具体到每个文件,占用多少.一目了然了。

4,最后,看看整个测试代码:

main.c代码如下,工程见附件.
#include "sys.h"
#include "usart.h"  
#include "delay.h" 
#include "led.h"
#include "beep.h"   
#include "key.h"   
//ALIENTEK战舰STM32开发板堆栈增长方向以及CPU大小端测试
//保存栈增长方向
//0,向下增长;1,向上增长.
static u8 stack_dir;
//CPU大小端
//0,小端模式;1,大端模式.
static u8 cpu_endian;
 

//查找栈增长方向,结果保存在stack_dir里面.
void find_stack_direction(void)
{
    static u8 *addr=NULL; //用于存放第一个dummy的地址。
    u8 dummy;               //用于获取栈地址
    if(addr==NULL)    //第一次进入
    {                         
        addr=&dummy;     //保存dummy的地址
        find_stack_direction ();  //递归
    }
    else                //第二次进入
 	{  
        if(&dummy>addr)
        	stack_dir=1; //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的.
        else 
        	stack_dir=0;           //第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的. 
 	}
}
//获取CPU大小端模式,结果保存在cpu_endian里面
void find_cpu_endian(void)
{
	 int x=1;
	 if(*(char*)&x==1)
 		cpu_endian=0; //小端模式
	 else 
 		cpu_endian=1;    //大端模式 
}
int main(void)
{   
	 Stm32_Clock_Init(9); //系统时钟设置
	 uart_init(72,9600);   //串口初始化为9600
	 delay_init(72);       //延时初始化
	 LED_Init();      //初始化与LED连接的硬件接口 
   	 printf("stack_dir:%x\r\n",&stack_dir);
   	 printf("cpu_endian:%x\r\n",&cpu_endian);
 
 	find_stack_direction(); //获取栈增长方式
	 find_cpu_endian();  //获取CPU大小端模式
 	 while(1)
	 {
 		 if(stack_dir)
 		 	printf("STACK DIRCTION:向上生长\r\n\r\n");
  		else 
  			printf("STACK DIRCTION:向下生长\r\n\r\n");
 		if(cpu_endian)
 			printf("CPU ENDIAN:大端模式\r\n\r\n");
 		 else 
 		 	printf("CPU ENDIAN:小端模式\r\n\r\n");
  		delay_ms(500);
  		LED0=!LED0; 
 	} 
}

测试结果如图:
在这里插入图片描述

  • 34
    点赞
  • 144
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值