本文基于Cortex‑M内核单片机(STM32)讲解,51单片机原理类似,适合嵌入式入门学习。编译链接后,程序会被划分成不同段(section),变量所属段决定了它存放在Flash还是SRAM。
一、存储器基础概念
Flash(闪存)
- 非易失性存储器:断电后数据不会丢失。
- 特性:读取速度快,擦写速度慢,需要按页擦除,程序运行期间Flash区域只读。
- 主要用途:存放程序代码、常量字符串、
const只读常量。
SRAM(静态随机存储器)
- 易失性存储器:断电数据全部丢失。
- 特性:读写速度极快,支持随机读写。
- 主要用途:存放全局变量、局部变量、栈、堆。
核心一句话:Flash存“样板/代码”,SRAM存“运行时可读写变量”
二、存储器直观示意图
Flash 存放代码与只读常量,SRAM存放运行时可读写变量。
Flash ┌────────────┬────────────┐ │ .text段 .rodata段 └────────────┴────────────┘ .text:程序代码指令 .rodata:const常量、字符串字面量(只读,断电不丢失) SRAM ┌──────────┬──────────┬──────────┬──────────┬──────────┐ │ .data段 .bss段 .heap段 .stack段 └──────────┴──────────┴──────────┴──────────┴──────────┘ .data用来存放有初始值的全局变量或静态变量 .bss用来存放无初始值或初始值为0的全局变量或静态变量 .heap段用来存放“堆”,malloc分配的内存 .stack段用来存放“栈”,局部变量,参数等(不包括静态局部变量)三、程序五大核心段
| 段名 | 存储位置 | 存放内容 | 初始化规则 |
|---|---|---|---|
.text代码段 | Flash | 函数指令、程序代码 | 烧录固化,运行只读 |
.rodata只读常量段 | Flash | const全局常量、字符串字面量 | 烧录固化,运行不可修改 |
.data已初始化全局变量段 | Flash存放初始镜像,上电拷贝到SRAM | 全局/静态变量,定义时赋初值(int a=10;) | MCU启动代码把Flash里的初始值复制到SRAM |
.bss未初始化全局变量段 | SRAM | 全局/静态变量,未初始化(int b;)或初始化为0(int a=0;) | 上电启动时,程序自动清零,不占用Flash空间 |
| 栈 Stack | SRAM | 局部变量、函数形参、返回地址、中断现场 | 函数调用时动态分配,函数退出自动回收,地址由高向低增长 |
| 堆 Heap | SRAM | malloc/free动态申请内存 | 手动申请释放,地址由低向高增长,嵌入式项目很少使用 |
四、C语言各类变量详细存储位置
1. 全局变量(函数外部定义)
int g_a = 100; // .data段:初始值100存Flash,上电拷贝到SRAM int g_b; // .bss段:位于SRAM,上电自动置0,不占用Flash const int g_c = 200; // .rodata段,Flash,只读不可修改- 生命周期:整个程序运行全程有效。
static修饰全局变量:仅限制作用域为本文件,存储段不变。
2. 静态局部变量 static(函数内部)
void test(void){ static int s_a = 10; // .data段,初始值在Flash,变量本体在SRAM;仅初始化1次 static int s_b; // .bss段,SRAM,上电清零 }⚠️ static局部变量不在栈中,存储规则和全局变量一致,作用域仅限当前函数。
3. 普通局部变量(函数内,无static)
void test(void){ int loc_a = 1; // 栈(SRAM),函数调用分配,函数结束销毁 char str[]="abc"; // 数组:字符串拷贝到栈内存,可以修改 const int loc_c=5; // 局部const:一般放在栈,部分编译器优化到rodata }⚠️ 单片机栈空间很小(通常几KB),禁止定义超大局部数组,极易栈溢出导致死机。
4. 字符串常量
char *p = "hello";"hello"字符串字面量存放在.rodata(Flash);指针变量p本身是局部变量,存放在栈(SRAM)。
尝试执行
p[0]='H'修改会触发硬件异常,Flash区域只读!
对比:
char str[]="hello";字符串初始样板存Flash,程序运行时拷贝到栈SRAM,str数组内容可以修改。
五、单片机上电启动流程(理解.data拷贝关键)
- 上电复位;
- 执行启动汇编文件
startup_stm32f10x.s:- 将
.data段的初始数据从Flash复制到SRAM; - 将
.bss段内存全部清零;
- 将
- 初始化堆栈指针SP;
- 调用
main(),正式运行C程序。
为什么.data要拷贝:Flash运行时只读,变量运行过程需要读写,因此初始样板保存在Flash,变量实体放到SRAM。
六、强制变量存放Flash(节省SRAM)
全局const常量默认存Flash,不占用SRAM。也可以通过GCC属性手动指定段:
// 存放在Flash,不占用SRAM,适合大查找表 const uint8_t table[] = {1,2,3,4,5};七、查看存储分布(验证方法)
编译后生成.map内存映射文件,可以直观查看各段占用Flash、SRAM大小:
- Keil MDK:开启map文件,查看
Memory Map of the image - STM32CubeIDE:编译输出.map文件,查看section地址与占用
- Code:Flash占用(text+rodata)
- RO-data:只读常量
- RW-data:.data段,SRAM占用
- ZI-data:.bss段,SRAM占用
八、总结
单片机C程序经过编译链接,会划分成多个存储段。.text代码段与.rodata只读常量段存储于Flash,断电数据不丢失。已初始化全局/静态变量归属.data段,初始镜像保存在Flash,上电拷贝至SRAM;未初始化全局、静态变量属于.bss段,位于SRAM,启动阶段自动清零。普通局部变量存放在SRAM栈空间,函数调用动态分配。SRAM属于易失存储器,断电数据丢失。 合理掌握变量存储位置,可以有效优化单片机RAM资源,规避栈溢出、内存访问异常等问题。