STM32大小端序与堆栈及其增长方向分析

发布者:Turquoise最新更新时间:2024-08-22 来源: elecfans关键字:STM32  大小端序  堆栈 手机看文章 扫描二维码
随时随地手机看文章

  在开源电子中看到一篇文章讲的是栈增长和大端/小端问题。学C语言的时候,我们知道堆栈的区别:

  (1)栈区(stack):由编译器自动分配和释放,存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈。

   (2)堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。分配方式类似于数据结构中的链表。

   (3)全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统自动释放。

   (4)文字常量区:常量字符串就是存放在这里的。

   (5)程序代码区:存放函数体的二进制代码。

  下面的帖子:主要意思是要证明stm32是小端模式,堆从RAM的起始地址处(0x2000 0000)分配内存给全局变量和静态变量,并且堆是向上增长,栈是向下增长。

  1,首先来看:栈(STACK)的问题。

  函数的局部变量,都是存放在“栈”里面,栈的英文是:STACK.STACK的大小,我们可以在stm32的启动文件里面设置,以战舰stm32开发板为例,在startup_stm32f10x_hd.s里面,开头就有:

  Stack_Size EQU 0x00000800

  表示栈大小是0X800,也就是2048字节。这样,CPU处理任务的时候,函数局部变量做多可占用的大小就是:2048字节,注意:是所有在处理的函数,包括函数嵌套,递归,等等,都是从这个“栈”里面,来分配的。

  所以,如果一个函数的局部变量过多,比如在函数里面定义一个u8 buf[512],这一下就占了1/4的栈大小了,再在其他函数里面来搞两下,程序崩溃是很容易的事情,这时候,一般你会进入到hardfault.。。。

  这是初学者非常容易犯的一个错误。切记不要在函数里面放N多局部变量,尤其有大数组的时候!

  对于栈区,一般栈顶,也就是MSP,在程序刚运行的时候,指向程序所占用内存的最高地址。比如附件里面的这个程序序,内存占用如下图:

  STM32大小端序与堆栈及其增长方向分析

  图中,我们可以看到,程序总共占用内存:20+2348字节=2368=0X940那么程序刚开始运行的时候:MSP=0X2000 0000+0X940=0X2000 0940.事实上,也是如此,如图:

  STM32大小端序与堆栈及其增长方向分析

  图中,MSP就是:0X2000 0940.程序运行后,MSP就是从这个地址开始,往下给函数的局部变量分配地址。

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

  [objc] view plain copy//保存栈增长方向

  //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”与栈区不同,堆区,则从内存区域的起始地址,开始分配给各个全局变量和静态变量。

  堆的生长方向,都是向上的。在程序里面,所有的内存分为:堆+栈。 只是他们各自的起始地址和增长方向不同,他们没有一个固定的界限,所以一旦堆栈冲突,系统就到了崩溃的时候了。

  同样,我们用附件里面的例程测试:

  STM32大小端序与堆栈及其增长方向分析

  stack_dir的地址是0X20000004,也就是STM32的内存起始端的地址。

  这里本来应该是从0X2000 0000开始分配的,但是,我仿真发现0X2000 0000总是存放:0X2000 0398,这个值,貌似是MSP,但是又不变化,还请高手帮忙解释下。

  其他的,全局变量,则依次递增,地址肯定大于0X20000004,比如cpu_endian的地址就是0X20000005.

  这就是STM32内部堆的分配规则。

  3,再说说,大小端的问题。

  大端模式:低位字节存在高地址上,高位字节存在低地址上

  小端模式:高位字节存在高地址上,低位字节存在低地址上

  STM32属于小端模式,简单的说,比如u32 temp=0X12345678;

  假设temp地址在0X2000 0010.

  那么在内存里面,存放就变成了:

  地址 | HEX |

  0X2000 0010 | 78 56 43 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,也就是小端模式。


  4,最后说说,STM32内存的问题。

  还是以附件工程为例,在前面第一个图,程序总共占用内存:20+2348字节,这么多内存,到底是怎么得来的呢?

  我们可以双击Project侧边栏的:Targt1,会弹出test.map,在这个里面,我们就可以清楚的知道这些内存到底是怎么来的了。在这个test.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)

  ==============================================================================

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

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

  main.c代码如下,工程见附件。

  [objc] view plain copy#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:%xrn”,&stack_dir);

  printf(“cpu_endian:%xrn”,&cpu_endian);

  find_stack_direction(); //获取栈增长方式

  find_cpu_endian(); //获取CPU大小端模式

  while(1)

  {

  if(stack_dir)printf(“STACK DIRCTION:向上生长rnrn”);

  else printf(“STACK DIRCTION:向下生长rnrn”);

  if(cpu_endian)printf(“CPU ENDIAN:大端模式rnrn”);

  else printf(“CPU ENDIAN:小端模式rnrn”);

  delay_ms(500);

  LED0=!LED0;

  }

  }


  测试结果如图:

   STM32 大小端序 与 堆栈及其增长方向分析

  有人提出全局变量和静态变量是存储在静态区而不是堆中,并且编写了一个测试代码,(原帖48楼)修改了堆和栈的长度,main函数增加了初始化串口缓冲的操作,和调用了一个无限迭代的函数。堆栈位置在《test.map》line:431-435和line:779-780.F11执行函数Iteration,会看到内存区会不断被Iteration的地址覆盖,直到破坏所有静态变量空间。如果静态变量在栈区,按照栈先进后出的机制,应该不会被破坏。当然,如果链接器是先放栈,再放堆,最后放静态变量,就做不了这个实验了。再三实验,也认可了这种说法,做了如下的修改:

  一、内存基本构成

  可编程内存在基本上分为这样的几大部分:静态存储区、堆区和栈区。他们的功能不同,对他们使用方式也就不同。

  静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。

  栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

  堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。

  按照这个说法,我在.s文件里面设置了:

  Heap_Size EQU 0x00000000

  也就是,没有任何动态内存分配。

  这样,内存=静态存储区+栈区了。

  不存在堆!!!

  因为我没有用malloc来动态分配内存。

  因此,前面提到的一切堆区,其实就是静态存储区。

  另外,经过测试,确实是这样。

  STM32的内存分配,应该分为两种情况。

  1,使用了系统的malloc。

  2,未使用系统的malloc。

  第一种情况(使用malloc):

  STM32的内存分配规律:

  从0X20000000开始依次为:静态存储区+堆区+栈区

  第二种情况(不使用malloc):

  STM32的内存分配规律:

  从0X20000000开始依次为:静态存储区+栈区

  第二种情况不存在堆区。

  所以,一般对于我们开发板例程,实际上,没有所谓堆区的概念,而仅仅是:静态存储区+栈区。

  无论哪种情况,所有的全局变量,包括静态变量之类的,全部存储在静态存储区。

  紧跟静态存储区之后的,是堆区(如没用到malloc,则没有该区),之后是栈区。


关键字:STM32  大小端序  堆栈 引用地址:STM32大小端序与堆栈及其增长方向分析

上一篇:浅谈STM32CubeMX使用方法及功能介绍
下一篇:stm32按键控制led灯亮灭

推荐阅读最新更新时间:2026-03-24 11:06

详细解析STM32中的堆栈机制
刚拿到STM32时,你只编写一个死循环 编译后,就会发现这么个程序已用了1600多的RAM,这要是在51单片机上,会心疼死了,这1600多的RAM跑哪儿去了,分析.map文件,你会发现是堆和栈占用的 在startup_stm32f10x_md.s文件中,它的前面几行就有以下定义: 这下明白了吧,STM32在启动的时候,RAM首先分配给使用到的全局变量,还有调用库占用的一些数据(不太清楚是什么数据),然后再将剩余的空间分配给Heap和Stack。由于内存空间是启动时实现分配好的,所以当动态分配内存的需求过多的时候,就会产生堆栈空间不足的问题。 查阅网上的资料,理解堆和栈的区别: - (1)栈区(stack):由编译器自动
[单片机]
详细解析<font color='red'>STM32</font>中的<font color='red'>堆栈</font>机制
关于STM32堆栈方面知识点
最近弄json,发现经常的堆溢出,然后找问题。因为对STM32堆栈问题没有深刻认识,就花时间好好研究下了堆栈并且做了验证 1.栈地址区间确定 首先找到启动文件,我的启动文件在startup_stm32f40xx.s,一般的启动文件也都在startup_stm32fxxxx.s文件里 __initial_sp 这个参数是栈顶地址,因为栈的增长是向下增长,所以这个参数我们可以理解为栈的起始地址,我设置的栈尺寸是0x400,所以栈的地址范围是 __initial_sp ~ (__initial_sp - 0x400),__initial_sp 这个参数是keil编译代码之后计算出来的,有多种方法确定。 2.确定__i
[单片机]
关于<font color='red'>STM32</font><font color='red'>堆栈</font>方面知识点
STM32学习笔记-STM32堆栈区(二)
STM32的分区 STM32的分区从0x2000 0000(0x2000 0000是SRAM的起始地址,由此可知,堆栈等都是在RAM中的)开始。静态区,堆,栈。所有的全局变量,包括静态变量之类的,全部存储在静态存储区。 紧跟静态存储区之后的,是堆区(如没用到malloc,则没有该区),之后是栈区,栈在程序中存储局部变量。 先看启动文件startup_stm32f10x_md.s的定义: ; Amount of memory (in bytes) allocated for Stack ; Tailor this value to your application needs ; h Stack Configurat
[单片机]
<font color='red'>STM32</font>学习笔记-<font color='red'>STM32</font><font color='red'>堆栈</font>区(二)
STM32的启动堆栈初始化
有几个问题,众多博文中抄来抄去,内容一样,却没有解释清楚 上电初始化堆栈,在进入_main后又说初始化堆栈,有什么不同 堆栈的地址是怎么得出来的 关于这两个问题,先借用一下要标准的启动流程 一般而言,系统上电后第一个执行的是由汇编所编写的启动文件,其主要工作为一下五部分: (1)、初始化堆栈指针SP=_initial_sp (2)、初始化PC指针,令其=Reset_Handler (3)、初始化中断向量表 (4)、配置系统时钟 (5)、调用C库函数_main初始化用户堆栈,从而最终调用main函数进入C的世界 STM32的中断向量表规定每一行必须是SP地址,第二行是复位中断入口地址,上电后,C
[单片机]
STM32 堆栈的理解
1、MDK STM32的内存分配 (摘自网络) 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编译之后能够得到的每个段的大
[单片机]
<font color='red'>STM32</font> <font color='red'>堆栈</font>的理解
STM32堆栈空间大小设置
1. 设置堆栈空间大小 在使用STM32编程时,一般情况下我们不会关注堆栈空间的大小,因为在STM32的启动文件中,已经帮我们预先设置好了堆栈空间的大小。如下图所示的启动代码中,Stack栈的大小为:0x400(1024Byte),Heap堆的大小为:0x200(512Byte)。 这也是为什么一个基础的工程编译后,RAM的空间也占用了1.6K左右的原因,因为堆栈的空间均分配在RAM中,可在编译的map文件中查看RAM资源占用的情况。 若工程中使用的局部变量较多,定义的数据长度较大时,若不调整栈的空间大小,则会导致程序出现栈溢出,程序运行结果与预期的不符或程序跑飞。这时我们就需要手动的调整栈的大小。 当工程中使用了
[单片机]
<font color='red'>STM32</font><font color='red'>堆栈</font>空间<font color='red'>大小</font>设置
STM32堆栈设置
1.堆和栈大小 定义大小在startup_stm32f2xx.s Stack_Size EQU 0x00000400 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 0x00000200 AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base
[单片机]
<font color='red'>STM32</font><font color='red'>堆栈</font>设置
STM32之程序如何防止堆栈溢出
近日为某个项目写了个草稿程序,即非正式程序,后来发现老是进入hardfaulthandler,原来是堆栈溢出,后仔细查看发现函数调用纵深太深,最多的时候可保持7个函数在堆栈中调用。 因此有心得如下: 一、函数调用不要纵深太深,即以下模式: main() { fun1(); } fun1() { fun2(); } fun2() { fun3(); } fun3() { fun4(); } fun4() { fun5(); } fun5() { fun6(); } fun6() { fun7(); } 这样子main函数要调用fun1函数完成某个功能,则要一直调到fun7为止,才能完成。这样导致堆栈中
[单片机]
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved