内存的分区及存放位置

一、内存分区介绍

程序中内存有以下分区:

  • 栈区(stack)

    • 临时创建的局部变量存放在栈区。

    • 函数调用时,其入口参数存放在栈区。

    • 函数返回时,其返回值存放在栈区。

    • const定义的局部变量存放在栈区。

  • 堆区(heap)
    堆区用于存放程序运行中被动态分布的内存段,可增可减。

    可以有malloc等函数实现动态分布内存。

    有malloc函数分布的内存,必须用free进行内存释放,否则会造成内存泄漏。

  • 全局区(静态区)(static)
    内存在程序编译的时候就已经分配好了,这块内存在程序的整个运行期间都存在。它主要存放静态数据(局部static变量,全局static变量)、全局变量。当程序结束后,变量由系统释放 。

  • 常量区
    存放数字 和 常量字符串。当程序结束后,由系统释放 。

    常量区的内容不可以被修改。

  • 代码区
    存放函数体的二进制代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//main.cpp
#include <iostream>
using namespace std;
#include <string.h>

int a = 0; //全局初始化区
char *p1; //全局未初始化区

int main(void)
{
int b; //栈
char s[] = "abc"; //"abc"在常量区,s在栈上。
char *p2; //栈
char *p3 = (char*)"123456"; //123456\0;在常量区,p3在栈上。
static int c = 0; //全局(静态)初始化区

p1 = (char *)malloc(10);
p2 = (char *)malloc(20); //分配得来的 10 和 20 字节的区域就在堆区。

strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与 p3 所指向的"123456"优化成一个地方。

delete p1, p2;
return 0;
}

二、各分区特点

1.全局静态变量

定义:在全局变量之前加上关键字static,全局变量就被定义为一个全局静态变量。

说明:1.内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)

2.初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非它被显示初始化)

3.作用域:全局静态变量在声明它的文件之外是不可见。准确的讲从定义之处开始到文件结尾。

注意:

1.不会被其他文件所访问,修改。

2.其它文件中可以使用相同名字的变量,不会发生冲突。

3.虽然改变了全局变量的作用域,但存储位置仍是静态存储区

2.局部静态变量

定义:在局部变量之前加上关键字static,局部变量就被定义为一个局部静态变量。

说明:1.内存中的位置:静态存储区

2.初始化:未经初始化的局部静态变量会被程序自动初始化为0(自动对象的值是任意的,除非它被显示初始化)

3.作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。

注意:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置,从原来的栈中存放改为静态存储区。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对它进行访问。

3.堆与栈的区别

1.管理方式不同:对于栈来讲,是由编译器自动管理的,无需手动控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。

2.空间大小不同:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小。在VC6下面,默认的栈空间是1M。

3.产生碎片不同:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,它们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出。在它弹出之前,在它上面的后进栈的内容已经被弹出。

4.生长方向不同:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。

5.分配方式不同:堆都是动态分配的,没有静态分配的堆。栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配是由alloca函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无需我们手工实现。

6.分配效率不同:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高

三、STM32中的内存存放

  • 由前面的分析我们知道,代码区和常量区的内容是不允许被修改的,ROM(STM32就是Flash Memory)也是不允许被修改的,所以代码区和常量区的内容编译后存储在ROM中

  • 而栈、堆、静态变量、全局变量都是存放在RAM中。

四、Keil 的Build Output窗口

如上图,存在Code、RO-data、RW-data、ZI-data四个代码段大小。

其中Code就是代码占用大小,RO-data是只读常量、RW-data是已初始化的可读可写变量,ZI-data是未初始化的可读可写变量。

有些时候,我们需要知道RAM和ROM的使用情况如何,那么我们就可以使用下面的公式计算。

  • RAM = RW-data + ZI-data
  • ROM = Code + RO-data + RW-data

注意:上面讲的内存分区(代码段、bss段…)虽然讲的是内存分区,但实际上有些段,比如代码段在STM32这种嵌入式设备中是不会被放到RAM中而是放到ROM中,这是因为嵌入式的RAM实在太小了…但是在非嵌入式平台,以上的内存分区的各段全是放在RAM中的。

五、STM32的堆栈

1.启动文件里堆栈大小都是以words为单位的(1words = 4 bytes)

2 裸机时Keil编译器工程项目的启动文件中定义的栈为:系统栈+用户栈(2个合二为一,作为一个整体)。系统栈+用户栈:单片机系统(中断函数和中断嵌套)使用这个栈区进行入栈和出栈操作;用户程序(函数调用的形参、非静态局部变量以及函数调用信息)也使用这个栈区进行入栈和出栈操作。

3  在FreeRTOS下,stm32的栈被分为:系统栈和任务栈.

  • 系统栈:只有单片机系统(中断函数和中断嵌套使用系统栈)在使用这个栈区(系统栈)进行入栈和出栈操作,这个栈不包含用户栈。c此时在启动文件里的那个stack只是系统栈。

  • 任务栈则是 由FreeRTOSConfig.h文件中的宏定义configTOTAL_HEAP_SIZE确定。任务栈本身就是一个全局数组,(FreeRTOS里的HEAP指的是栈,而并不是堆。。。)

  • 工程项目中创建的每一个任务的栈区(包括:每个任务的函数调用的形参、非静态局部变量以及函数调用信息)都是从 FreeRTOS内核的heap_4.c中定义HEAP区中进行申请和分配。

  • 这个HEAP(用户栈区)来自于静态数组,所以它存在于数据段(具体为.bss段), 它和Keil编译器工程项目的启动文件中定义的堆不是一个概念。

  • FreeRTOS编程时,使用C语言的库函数malloc函数动态请求分配堆区,对于微控制器并不是线程安全的。使用库函数free函数释放空间,对于微控制器并不是线程安全的。为了线程安全,在使用FreeRTOS编程时,进行动态内存申请(Keil编译器工程项目的启动文件中配置的堆区)时,请使用FreeRTOS提供的pvPortMalloc和vPortFree函数。(还是从系统的堆区里申请,并不是FreeRTOS的HEAP)

参考地址:

STM32F103移植FreeRTOS必须搞明白的系列知识—3(堆栈)_ba_wang_mao的博客-CSDN博客