【嵌入式内存分布】
本篇主要讲嵌入式中内存分布情况。
嵌入式内存分布
本篇主要讲嵌入式中内存分布情况。
一、内存分区
内存分区示意图如下:
1. 代码区
含义:
- 这是程序的可执行代码部分的大小,通常对应于
.text段。
内容:
- 包含所有编译后的机器指令,即 MCU 将执行的代码。
- 字符串常量和define定义的常量存放在代码区,它们在预处理过程就会被替换成对应常量直接嵌入到代码中,最终链接为机械指令,也就是上点所说保存在.text段。
- 程序执行代码存放在代码区,其值不能修改(若修改则会出现错误)。
存储位置:
- Flash(闪存)。因为 Flash 是非易失性存储器,断电后数据仍然保留,适合存储程序代码。
2. 常量区
含义:
- 这是程序中只读数据的大小,通常对应于
.rodata段。
内容:
包含所有的常量数据,例如 :
- 字符串、数字等常量存放在常量区。
- const修饰的全局变量存放在常量区。
- 程序运行期间,常量区的内容不可以被修改。
存储位置:
- Flash(闪存)。因为这些数据是只读的,存储在 Flash 中可以节省 RAM,并保证数据的持久性。
3. 全局(静态)区
全局(静态)区介绍
- 编译器编译时即分配内存,全局变量和静态变量的存储是放在一块的。C语言中,已初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
- 全局区有 .bss段 和 .data段组成,可读可写。
.bss段
- 未初始化的全局变量和未初始化的静态变量存放在.bss段。
- 初始化为0的全局变量和初始化为0的静态变量存放在.bss段。
- .bss段不占用可执行文件空间,其内容由操作系统初始化(清零)。
存储位置:
- SRAM(静态随机存取存储器)
- **Flash 中不占用空间:**因为这些变量的初始值为零,不需要在 Flash 中存储。
- **程序启动时:**启动代码会将对应的 RAM 区域清零。
.data段
- 已初始化的全局变量存放在.data段。
- 已初始化的静态变量存放在.data段。
- .data段占用可执行文件空间,其内容由程序初始化。
存储位置:
- Flash(闪存)和 SRAM(静态随机存取存储器)
- **Flash 中:**存储这些变量的初始值。
- **SRAM 中:**在程序启动时,初始值从 Flash 复制到 RAM 中,以便在运行时可以修改这些变量。
4. 堆区(heap)
堆区介绍
- 堆区由程序员分配内存和释放。
- 堆区按内存地址由低地址到高地址增长,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。
注意:堆内存牢记不忘释放,避免内存泄漏的情况。
调用函数
- 用malloc等函数实现动态分布内存。
void *malloc(size_t);
参数size_t是分配的字节大小。
返回值是一个void型的指针,该指针指向分配空间的首地址。
(void *型指针可以任意转换为其他类型的指针)
- 用free函数进行内存释放,否则会造成内存泄漏。
void free(void * /ptr/);
参数是开辟的内存的首地址。
5. 栈区(stack)
栈区介绍
- 栈区由编译器自动分配释放,由操作系统自动管理,无须手动管理。
- 栈区上的内容只在函数范围内存在,当函数运行结束,这些内容也会自动被销毁。
- 栈区按内存地址由高地址到低地址方向增长,其最大大小由编译时确定,速度快,但自由性差,最大空间不大。
- 栈区是先进后出原则,即先进去的被堵在屋里的最里面,后进去的在门口,释放的时候门口的先出去。
存放内容
- 临时创建的局部变量和const定义的局部变量存放在栈区。
- 函数调用和返回时,其入口参数和返回值存放在栈区。
二、STM32存储器分配

随机存储器—RAM
- RAM是与CPU直接交换数据的内部存储器,也叫主存(内存)。
- 它可以随时读写,而且速度很快,通常作为操作系统或其他正在运行中的程序的临时数据存储媒介。
- 当电源关闭时RAM不能保留数据(掉电数据消失哦)如果需要保存数据,就必须把它们写入一个长期的存储设备中(例如硬盘)。
2. 只读存储器—ROM
- 虽然叫做“只读存储器”,但实际上像EPROM跟EEROM是支持可重复写入的,之所以叫这个名字,跟它的起源有关。
- 读取速度相比RAM要慢
- 断电记忆存储功能。
ROM与Flash是什么关系
(1)上个世纪,发明了两种存储器,一个是RAM,一个是ROM。当时的ROM一旦存储数据就再也无法将之改变或者删除,也就是说,程序只有一次下载机会。如果你程序写错了,那么这个芯片就报废了。很明显非常的鸡肋。
(2)为了便于使用和大批量生产,进一步发展出了可编程只读存储器(PROM)、可擦除可编程只读存储器(EPROM)。EPROM需要用紫外线长时间照射才能擦除,使用很不方便。
(3)1980s又出现了电可擦除可编程只读存储器(EEPROM),它克服了EPROM的不足,但是集成度不高、价格较贵。
(4)于是又发展出了一种新型的存储单元结构同EPROM类似的快闪存储器(FLASH MEMORY)。FLASH集成度高、功耗低、体积小,又能在线快速擦除,因而获得了快速发展。关系图如下

(5)因此,现在的单片机存储数据主要是使用的Flash,就是替代以前的ROM,最大的有有点是降低了芯片的成本并且可以做到电擦写。但是因为最先发展出来的是ROM,所以很多时候,人们常常愿意说芯片里面的是ROM存储代码,而不是说Flash存储代码。我们需要了解到这个即可,随便你怎么讲。我们只需要知道,现在的芯片都是用Flash存储数据,但是也要能够明白,很多人讲的ROM就是Flash。
断电和通电状态下的数据存储
为什么要分断电和通电状态分析
(1)在一款MCU中,一个程序编译完成之后,需要烧录进去。这个程序有代码段,只读数据,可读可写数据。上文我们分析了,代码段和只读数据存储在ROM,那么可读可写的数据存储在哪里呢?难道是RAM吗?
(2)这些可读可写的数据有被赋予初值的变量。假设,这些变量存储在RAM,会发生什么?
(3)很明显,这些被赋予初值的变量存储在RAM,那么一旦断电了,这些初值将会消失!也就是说,我们的这些初值没了!
(4)由此可见,我们数据存储要分成断电和通电两种状态进行分析。而我们上面讲了那么多,其实都是以通电状态进行数据存储的分析的。
断电状态数据存储
(1)从上面的分析我们可以知道,虽然在通电状态下,可读可写数据是要存储在RAM的,但是在断电状态还是要存储在ROM的。所以,现在我就以STM32的断电和通电存储来进行讲解。
(2)我们对一个STM32的工程文件进行编译,会出现如下的编译结果。其中有一行是Program Size: Code=5984 RO-data=336 RW-data=56 ZI-data=1024 。
(3)
<1>Code : 代码段,存放程序的代码部分。
<2>RO-data:只读数据段,存放程序中定义的常量。
<3>RW-data:读写数据段,存放初始化为非 0 值的全局变量。
<4>ZI-data:零初始化的可读写变量,存放未初始化的全局变量及初始化为 0 的变量。

.bss 段不占空间的原因:
- 在编译时,
.bss段的变量并没有实际的初始值,因为这些变量在程序启动时会自动初始化为 0。因此,编译器只需要记录这些变量的大小,而不需要为它们分配额外的存储空间(比如在 Flash 中)。- 不占用 Flash:由于
.bss段中的数据没有初始值,编译器不需要在 Flash 中保存它们的初始化数据。符号表的作用:
- 编译器会为每个变量分配一个地址,这个地址被记录在符号表中,用于在程序执行时访问变量。符号表在编译过程中使用,但不会被写入最终的可执行文件中。
- 在程序执行时,变量的地址存储在代码中,程序通过这些地址访问相应的内存。
程序执行时分配内存:
- 当程序启动时,启动代码(通常称为 C 运行时环境初始化代码,或 CRT)会在 RAM 中为
.bss段的变量分配内存,并将其全部初始化为 0。因此,.bss段中的变量在执行时才会占用 SRAM 空间,而不是在编译时占用 Flash 空间。.data段占用空间解析:
.data这些变量在程序开始执行前就有明确的初始值,因此编译时需要在 Flash 中存储这些初始值,如果只是存储在RAM中,那么掉电会丢失。在程序启动时,启动代码会将这些初始值从 Flash 复制到 RAM 中,以便程序运行时可以修改这些变量。
因此,.data 段的初始值会占用 Flash 空间,并且在程序运行时占用 RAM 空间。
由此得知,在断电状态,数据都存储在了ROM中,如下。(注意,这张图没有出现Code,但是我们还是需要知道Code数据存储在ROM中)

通电状态数据存储
(1)通过上面的分析我们知道了断电和通电状态下的数据存储了。那么上电的一瞬间发生了什么?
(2)RO-Data数据不变,RW-Data的数据被复制到RAM区域,RAM中划分出一个ZI-data区域。当程序执行过程中,被初始化为0的数据,CPU会向ZI-data对应的变量中写入0。没有被初始化的数据,那么就是随机值。这也是为什么我们初学C语言的时候,反复强调要进行初始化。但是keil帮我们做了很多事情,如果你变量没有被初始化,他在划分ZI-data区域的时候,会把这个区域全部清零。
(3)RO-Data的数据就是常量区的数据,RW-Data对应静态区的数据段,ZI-data对应静态区的BSS段。那么堆栈呢?
(4)堆栈是由启动文件划分的,如果你感兴趣,可以打开STM32的启动你文件startup_stm32f10x_hd.s。
单片机中的内存分布
在keil中编译程序完成后,在最下面的“Build Output”输出栏,可以看到有这样一行信息“Program Size: Code=321676 RO-data=35972 RW-data=4788 ZI-data=126284”,这句话提供了程序在编译后的大小信息,包括Code、RO-data、RW-data和ZI-data的大小。这些大小信息对开发人员和嵌入式系统设计者来说具有以下作用:
- 内存优化:通过了解不同部分的大小,可以评估程序对内存的占用情况,帮助进行内存优化。例如,可以通过减少代码大小、优化数据存储方式或优化算法来减少内存的使用,从而节约系统资源。
- 内存规划:了解程序的Code、RO-data、RW-data和ZI-data大小,可以帮助进行内存规划。在嵌入式系统设计中,内存资源是有限的,因此需要合理地分配内存空间来满足程序的需求。通过了解不同部分的大小,可以确定它们在内存中的布局和分配方式,以避免冲突或浪费。
- 系统可靠性:内存的合理管理对系统的可靠性至关重要。通过了解程序的大小,特别是RO-data和RW-data的大小,可以确保所使用的内存空间不会超出设备的可用内存范围,避免内存溢出和相关的错误。
- 调试和故障排查:Program Size提供的信息可以用于调试和故障排查。如果程序运行时出现问题,例如崩溃或性能问题,可以通过检查不同部分的大小和内存使用情况来定位问题所在,并分析是否存在内存相关的错误或瓶颈。
能更好的帮助开发人员在嵌入式系统设计中更好地管理内存资源,并确保程序在目标设备上正常运行。
参考文档
[C语言+单片机-内存分布详解,全网最全,值得收藏保存_单片机内存划分-CSDN博客](https://blog.csdn.net/Wang_XB_3434/article/details/131318213)
[RAM明明断电会丢失数据,为什么初始化的全局变量存储在RAM?详细分析程序的存储-云社区-华为云 (huaweicloud.com)](https://bbs.huaweicloud.com/blogs/407311)
[ROM、RAM、FLASH、DDR、EMMC 百科 – 学习笔记 - 二的次方 - 博客园 (cnblogs.com)](https://www.cnblogs.com/roger-yu/p/16655061.html)
更多推荐




所有评论(0)