STM32堆栈区

来源:互联网 发布:android 监听数据变化 编辑:程序博客网 时间:2024/05/18 04:43

三、STM32堆栈区

预备知识:

一个由C/C++编译的程序占用的内存分为以下几个部分:

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

l  堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

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

l  文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放

l  程序代码区—存放函数体的二进制代码

编译后,各个区存储内容举例说明如下:

//main.cpp

int a = 0; 全局初始化区

char *p1; 全局未初始化区

main()

{

int b;

char s[] = “abc”;

char *p2;

char *p3 = “123456”;123456\0在常量区,p3在栈上

static int c =0; 全局(静态)初始化区

p1 = (char *)malloc(10);

p2 = (char *)malloc(20);

分配得来得1020字节的区域就在堆区

strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"

优化成一个地方。

}

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 Configuration 
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8> 
; </h>

Stack_SizeEQU 0x00000400

AREASTACK, NOINIT, READWRITE, ALIGN=3 
Stack_Mem SPACE Stack_Size 
__initial_sp


; <h> Heap Configuration 
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8> 
; </h>

Heap_SizeEQU 0x00000200

AREAHEAP, NOINIT, READWRITE, ALIGN=3 
__heap_base 
Heap_Mem SPACE Heap_Size 
__heap_limit

这里定义了堆栈各自大小,堆:512bytes1k

所以栈区大小有限制,我们在局部变量中不要定义大数组否则容易溢出

再看下code ro rw zi

l  Code指存储到flash【Rom】中的程序代码。

l  ZI英语是zero initial,就是程序中用到的变量并且被系统初始化为0的变量的字节数,keil编译器默认是把你没有初始化的变量都赋值一个0,这些变量在程序运行时是保存在RAM中的

l  RW是可读可写变量,就是初始化时候就已经赋值了的,RW + ZI就是你的程序总共使用的RAM字节数

l  RO是程序中的指令和常量,这些值是被保存到Rom中的

l  Total ROM Size (Code +RO Data + RW Data)这样所写的程序占用的ROM的字节总数,也就是说程序所下载到ROM flash 中的大小。为什么Rom中还要存RW,因为掉电后RAM中所有数据都丢失了,每次上电RAM中的数据是被重新赋值的,每次这些固定的值就是存储在Rom中的,为什么不包含ZI段呢,是因为ZI数据都是0,没必要包含,只要程序运行之前将ZI数据所在的区域一律清零即可。包含进去反而浪费存储空间。 

         实际上,ROM中的指令至少应该有这样的功能: 
         1. 将RW从ROM中搬到RAM中,因为RW是变量,变量不能存在ROM中。 
         2. 将ZI所在的RAM区域全部清零,因为ZI区域并不在Image中,所以需要程序根据编译器给出的ZI地址及大小来将相应得RAM区域清零。ZI中也是变量,同理:变量不能存在ROM中。 

 

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

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

Stack_Size     EQU     0x00000800

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

所以,如果一个函数的局部变量过多,比如在函数里面定义一个u8 buf[512],这一下就占了1/4的栈大小了,再在其他函数里面来搞两下,程序崩溃是很容易的事情,这时候,一般你会进入到hardfault....
这是初学者非常容易犯的一个错误.切记不要在函数里面放N多局部变量,尤其有大数组的时候!

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

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

图中,MSP就是:0X2000 0940,程序运行后,MSP就是从这个地址开始,往下给函数的局部变量分配地址。再说说栈的增长方向,我们可以用如下代码测试:

//保存栈增长方向

//0,向下增长;1,向上增长

static u8 stack_dir;

//查找栈增长方向,结果保存在stack_dir里面

void find_stack_direction(void)

{
    static u8 *addr=NULL;   //用于存放第一个dummy的地址。
    u8dummy;                  //用于获取栈地址 
    if(addr==NULL)         //第一次进入
   {                          
        addr=&dummy;       //保存dummy的地址
        find_stack_direction (); //递归 
    }

else               //第二次进入 
  {  
       if(&dummy>addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的. 
        elsestack_dir=0;           //第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的.  
 }

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

2,再来说说,(HEAP)的问题.

全局变量,静态变量,以及内存管理所用的内存,都是属于“堆"区”,英文名:“HEAP”,与栈区不同,堆区,则从内存区域的起始地址开始分配给各个全局变量和静态变量。

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

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

这里本来应该是从0X2000 0000开始分配的,但是,我仿真发现0X2000 0000总是存放:0X2000 0398,这个值,貌似是MSP,但是又不变化,还请高手帮忙解释下。其他的,全局变量,则依次递增,地址肯定大于0X20000004,比如cpu_endian的地址就是0X20000005。这就是STM32内部堆的分配规则.

3,再说说,大小端的问题.
大端模式:低位字节存在高地址上,高位字节存在低地址上 
小端模式:高位字节存在高地址上,低位字节存在低地址上

STM32属于小端模式,简单的说,比如u32temp=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,也就是小端模式.


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

我们可以双击Project侧边栏的:Targt1,会弹出test.map,在这个里面,我们就可以清楚的知道这些内存到底是怎么来的了.在这个test.map最后,Image 部分有:
==============================================================================

Image component sizes


 Code (inc. data)   ROData    RW Data    ZIData      Debug   Object Name

      172        10             0              4            0       995   delay.o//delay.c里面,fac_usfac_ms,共占用4字节
      112        12         0         0         0          427   led.o
        72         26       304         0      2048        828  startup_stm32f10x_hd.o  //启动文件,里面定义了Stack_Size0X800,所以这里是2048.
      712        52         0         0         0       2715   sys.o
       348       154         0         6         0     208720   test.o//test.c里面,stack_dircpu_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)   ROData    RW Data    ZIData      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    ZIData      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 + ROData)                4296 (   4.20kB)
    Total RW  Size (RW Data + ZIData)             2368 (   2.31kB)   //总共占用:2248+20+100=2368.
    Total ROM Size (Code + RO Data + RWData)       4316 (   4.21kB)

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

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

原创粉丝点击