C/C++内存分布
- 一、内存组成
- 二、静态区域
- 文本段 (Text / 只读区域 RO)
- 已初始化读写数据段(RW data -- Initialized Data Segment)
- 未初始化数据段(BSS -- Block Started by Symbol)
- 三、动态区域
- 堆(heap)
- 栈(stack)
- 四、STM32 内存分析
- Flash区域
- RAM区域
- map文件分析
一、内存组成
根据动静特性可以将内存分为动态区域和静态区域,代码段(Code)、只读数据段(RO data)、读写数据段(RW Data)、未初始化数据段(BSS)属于静态区域。堆和栈属于动态区域。
二、静态区域
文本段 (Text / 只读区域 RO)
通常代码段和只读数据段合成为文本段(Text), 包含实际要执行的代码(机器指令)和 常量(常量区Ro data),例如字符串常量等。它通常是共享的,多个实例之间共享文本段。文本段是不可修改的。
已初始化读写数据段(RW data – Initialized Data Segment)
已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并具有初值,以供程序运行时读写。
未初始化数据段(BSS – Block Started by Symbol)
未初始化的全局变量和静态变量,程序运行之前不需要占用存储器的空间,BSS段的变量只有名称和大小却没有值。
BSS段主要是为了节省可执行文件在磁盘上所占的空间,其仅仅记录变量所需的大小。储存未初始化的,或初始化为0的全局变量和静态变量。 BSS段属于静态内存分配,所以放在RAM里。
对未初始化的大型数组的节省效率比较明显。举例如下:
static int a[10000];
int main()
{
}
在上述程序中,若不存在 BSS 段,则可执行文件将开辟一个 10000 * sizeof(int) 大小的空间,并全部存储为0,int 为4字节的情况下,该变量将在磁盘上占用39KB的空间。但是此时若是存在BSS 段,则在可执行文件中,将只是记录现在的BSS段总大小为40000即可,而无需真正的占据39KB的空间.
代码优化对BSS段的影响:全局变量与静态变量没有初始化或初始化值为0时,都会放在.bss段。初始化为非0值,则放在.data段。考虑以下两个静态变量分别存储在哪个段中:
static int x1 = 1;
static int x2 = 0;
很明显可以看出,X1将被发在.data段中。令人意外的是 X2 将被放置在 .bss 段中,因为 x2 的值为0,被认为是未初始化的,因此将会被放在 .bss 段中以节省磁盘空间。
三、动态区域
对于程序运行过程中的内存使用,堆和栈一般是相向扩展的。堆的分配由程序来分配,但是栈是由编译器管理的。
堆(heap)
-
堆内存只在程序运行时出现,一般由程序员分配和释放。在具有操作系统的情况下,如果程序没有释放,操作系统可能在程序(例如一个进程)结束后回收内存。注意它与数据结构中的堆是两回事,其操作方式类似于数据结构中的链表。
-
当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);
-
当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
FreeRTOS的内存分配有五个方案: 如果申请的内存空间都在一个连续的空间内heap_4就够用了,但如果存在部分空间申请在内部RAM、部分在外部RAM,这时候就需要使用heap_5方案了,heap_5是在heap_4基础上实现的。
特点 | 缺点 | |
---|---|---|
heap_1 | 简单、不支持内存释放 | 需要管理内存空间 |
heap_2 | 支持内存释放,不支持碎片管理 | 需要管理内存空间、碎片问题 |
heap_3 | malloc-free操作简单 | 碎片问题 |
heap_4 | 支持碎片管理 | 需要管理内存空间 |
heap_5 | 支持多个不连续内存空间,碎片管理 | 需要管理内存空间 |
栈(stack)
栈内存只在程序运行时出现,在函数内部使用的局部变量、函数的参数以及返回值将使用栈空间,栈空间由编译器自动分配和释放。其操作方式类似于数据结构中的栈。
例一:静态存储区、栈区、堆区
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
char *a = "hello 1"; //静态存储区
char b[] = "hello 2"; //栈区
char *c = (char *)malloc(sizeof(b)); //堆区
memcpy(c, "hello 3", sizeof(b));
//a[2] = 'C'; //操作静态区会报错,只读不可修改
b[2] = 'C'; //操作栈-修改成功
c[2] = 'C'; //操作堆-修改成功
printf(" a:%p %s\n b:%p %s\n c:%p %s\n",a,a,b,b,c,c);
return 0;
}
程序运行结果如下
a:0000000000404000 hello 1
b:000000000061FE08 heClo 2
c:00000000007F1400 heClo 3
四、STM32 内存分析
在对于RAM紧缺的嵌入式系统中,是缺少MMU内存管理单元的。因此在一些嵌入式系统中,比如常用的STM32来讲,内存映射被划分为闪存段(也被称为Flash,用于存储代码和只读数据)和 RAM段,用于存储读写数据。
在《ARM Cotrex-M3权威指南》中有关 M3的存储器映射表:
STM32的Flash和RAM地址范围,从图中我们可以看出,RAM地址是从0x2000 0000开始的,Flash地址是从0x8000 0000开始的。
Flash区域
如上图所示,Flash又可以分为这么几个部分。
- 分别是文本段(Text) ,其中文本段中又包含可执行代码(Executable Code)和常量(Literal Value),在文本段之后就是只读
- 数据区域(Read Only Data),当然并不是所有架构的单片机都满足这样一个排布规律,这里只针对于ARM Cortex M3系列的
- 只读数据段后面接着的就是数据复制段(Copy of Data Section),第一次遇见这个概念的朋友看到数据复制可能会有疑惑,其实这个段充当的作用是存放程序中初始化为非0值得全局变量的初始值之所以要将初始值存放到这里 是因为全局变量是存放在RAM上的,RAM上的值掉电便丢失,每次上电之后这些变量是要重新赋值的,而重新赋值的值就存放在这里 那为什么不存放初始化为0的全局变量的初始值呢,原因也很简单,既然是初始化为0,那么在上电之后统一对存放初始化为0的全局变量的那块区域清0就好。
RAM区域
如上图所示,RAM中包含了如下几个部分,
- 栈(Stack):存放局部变量和函数调用时返回的地址
- 堆(heap):由malloc申请,free释放
- bss:存放未初始化或者是初始化为0的全局变量
- data:存放初始化为非0的全局变量
map文件分析
Code | RO Data | RW Data | ZI Data |
---|---|---|---|
Executable | Code | Read Only | Data data |
-
程序占用 Flash = Code + RO data + RW data
-
程序运行时候占用 RAM = RW data + ZI data
-
Code + RO data + RW data 的大小也是生成的 bin 文件的大小