嵌入式开发基础知识补充—内存的种类和C语言变量的存储位置(非易失性存储和易失性存储)
我们就用一个''开公司''的比喻,来帮你彻底搞懂它们的关系。
第一部分:硬件基础 - 公司的“仓库”与“办公室”
在MCU这个微型“公司”里,有两种最核心的物理资产:Flash 和 SRAM。
Flash (闪存) - 公司的“档案仓库”
是什么? Flash是一种非易失性存储器 (Non-Volatile Memory)。
核心特性:“非易失”意味着断电后数据不会丢失。就像仓库里的档案柜,即使公司下班断电了,第二天来文件还在里面。
作用:
存放程序代码 (Code): 你编译后生成的
.bin
或.hex
文件,也就是MCU要执行的所有指令,都烧录在这里。这是公司的**“操作手册和规章制度”**。存放常量数据 (Constant Data): 代码中定义的常量,比如
const int a = 100;
或者字符串"Hello World"
,也存放在这里。这些是**“不可更改的档案资料”**。
特点:
容量大: 通常MCU的Flash容量会比SRAM大很多(如512KB Flash vs 128KB SRAM)。
速度慢: CPU读取Flash的速度远慢于读取SRAM。
擦写次数有限: Flash有写入寿命限制(通常是1万到10万次),不适合频繁改写。
SRAM (静态随机存取存储器) - 公司的“办公区工位”
是什么? SRAM是一种易失性存储器 (Volatile Memory)。
核心特性:“易失”意味着断电后数据会瞬间全部丢失。就像办公桌,下班人走清空,第二天来是干净的。
作用:
存放变量 (Variables): 程序运行时,所有需要被修改、计算、暂存的数据,都放在SRAM里。这是员工(CPU)进行日常工作的**“办公桌面”**。
作为函数调用栈 (Stack): 函数调用、参数传递等,都在这里进行。
特点:
容量小: 因为其物理结构复杂且昂贵,容量通常比Flash小。
速度极快: CPU可以直接高速读写SRAM,几乎没有延迟,保证了程序的运行效率。
擦写次数近乎无限: 可以随意、频繁地读写。
总结一下硬件的区别:
特性 | Flash (仓库) | SRAM (办公室) |
断电后数据 | 保留 (非易失) | 丢失 (易失) |
主要用途 | 存储程序和常量 | 存储变量和运行状态 |
读写速度 | 慢 | 极快 |
容量大小 | 大 | 小 |
擦写寿命 | 有限 | 近乎无限 |
导出到 Google 表格
第二部分:C语言映射 - “员工”如何使用“仓库”和“办公室”
现在,我们来看看你写的C代码,是如何被分配到Flash和SRAM这两块地方的。MCU程序运行时的内存(主要是SRAM),通常被划分为以下几个区域:
存放在SRAM (办公室) 的内容:
.data段 (已初始化数据段)
存放什么: 全局变量和静态变量(
static
),且被初始化为非零值。C语言示例:
int global_var = 100;
static int static_var = 50;
工作流程: 这些变量的初始值
100
和50
其实是先存放在Flash里的。MCU上电后,在执行main
函数之前,会有一段启动代码先把这些值从Flash拷贝到SRAM的.data
段。就像上班前,员工从仓库档案柜里拿出今天要用的资料,放到自己的办公桌上。
.bss段 (未初始化数据段)
存放什么: 全局变量和静态变量,但它们没有被初始化或被初始化为0。
C语言示例:
int global_uninit_var;
static int static_zero_var = 0;
工作流程: 启动代码在初始化
.bss
段时,会直接把它整块区域清零。这样做可以节省Flash空间,因为不需要在Flash里存储一大堆的0。
Heap (堆)
存放什么: 程序动态申请的内存。
C语言示例: 通过
malloc()
,calloc()
等函数申请的内存空间,用完后需要free()
手动释放。int* p = (int*)malloc(4);
这里的p
本身是个局部变量在栈上,但它指向的那4个字节的内存在堆上。特点: 堆是动态的、不连续的,使用不当容易产生内存碎片或泄漏。在很多裸机MCU项目中,为了稳定性和实时性,会尽量避免使用堆。
Stack (栈)
存放什么:
函数的局部变量:
void func() { int local_var = 10; }
函数的参数
函数的返回地址
工作流程: 栈是一个“先进后出”的结构。每调用一个函数,系统就会在栈顶为它分配一块空间(称为“栈帧”)来存放它的局部变量等信息。函数返回时,这块空间会被自动释放。这是由编译器自动管理的,效率极高。
存放在Flash (仓库) 的内容:
.text段 (代码段)
存放什么: 程序的二进制代码。你编写的所有函数,最终都会变成机器指令存放在这里。
.rodata段 (只读数据段)
存放什么: 只读的常量数据。
C语言示例:
const int const_var = 10;
以及字符串字面量,如printf("Hello World");
里的"Hello World"
。
// a.c 文件
#include <stdlib.h>const int g_const_var = 10; // 存放在 Flash 的 .rodata 段
char* g_str = "Hello"; // g_str 指针本身在 .data 段 (SRAM)// "Hello" 字符串常量在 .rodata 段 (Flash)int g_init_var = 100; // 存放在 SRAM 的 .data 段 (初始值100来自Flash)
static int s_uninit_var; // 存放在 SRAM 的 .bss 段 (上电后被清零)void my_func(int param) { // 函数代码本身在 Flash 的 .text 段int local_var = 20; // local_var 和 param 都在 SRAM 的 栈(Stack)上int* heap_var = (int*)malloc(4); // heap_var指针在栈上, 指向的那4字节在堆(Heap)上*heap_var = 30;free(heap_var);
}int main(void) {my_func(g_init_var);return 0;
}
全局变量存储在哪里?
全局变量的存储位置取决于它是如何被定义的,主要分为以下三种情况:
1. 已初始化的全局变量 (Initialized Global Variables)
这种变量在定义时就被赋予了一个非零的初始值。
C语言示例:
Cint g_init_var = 100; char g_char_array[] = "Hello";
存储位置: 运行时存储在 SRAM 的
.data
段。详细说明: 它的初始值(比如
100
和"Hello"
)是固化在Flash里的。当MCU上电启动时,在进入main
函数之前,一段特殊的启动代码会负责把这些初始值从Flash拷贝到SRAM中的.data
段区域。程序在运行时,实际读写的是SRAM中的这个变量。
2. 未初始化 或 初始化为0的全局变量 (Uninitialized or Zero-Initialized Global Variables)
这种变量在定义时没有初始值,或者初始值为0。
C语言示例:
Cint g_uninit_var; static int g_zero_var = 0;
存储位置: 运行时存储在 SRAM 的
.bss
段。详细说明: 为了节省Flash空间,编译器并不会在Flash里存储这些变量的初始值(0)。启动代码在初始化
.bss
段时,会直接将这整块内存区域全部清零。
3. const
修饰的全局常量 (const-qualified Global Constants)
这种变量被const
关键字修饰,意味着它的值在程序运行期间不能被修改。
C语言示例:
Cconst int g_const_var = 10; const char* message = "This is a constant string";
存储位置: 直接存储在 Flash 的
.rodata
(只读数据) 段。详细说明: 因为它的值是只读的,没有必要在运行时把它拷贝到宝贵的SRAM中。CPU在需要读取这个常量时,会直接从Flash中读取。
总结表格
为了方便您记忆,这里有一个清晰的总结:
变量定义示例 | 运行时存储位置 | 内存区域 (Section) |
int g_init_var = 100; | SRAM | .data |
int g_uninit_var; | SRAM | .bss |
const int g_const_var = 10; | Flash | .rodata |
核心要点回顾: 只要不是const
修饰的全局变量,它在程序运行时最终都会存在于SRAM中,以便于程序进行快速的读写操作。区别仅在于它的初始值是如何被加载的。