1 单片机内存划分总览
内存区域 |
存储内容 |
特性 |
FLASH (Text) |
代码(.text)、常量(.rodata) |
只读,掉电不丢失 |
RAM |
.data(已初始化全局变量) |
读写,掉电丢失 |
.bss(未初始化全局变量) |
初始化为0,占用RAM空间 |
|
堆(Heap)(动态内存分配) |
手动分配释放(如malloc) |
|
栈(Stack)(局部变量、函数调用) |
自动分配释放,大小需预先配置 |
1. .text 段(代码段)
定义与特性
代码示例
// 代码本身编译后位于.text段
int main() {
return 0;
}
// const常量位于.rodata段(.text的子段)
const uint32_t FIXED_VALUE = 0x12345678; // 存储在FLASH中
关键点
2. .data 段(已初始化数据段)
定义与特性
代码示例
uint32_t global_var = 0xABCD; // 全局变量,位于.data段
void func() {
static int static_var = 100; // 静态局部变量,也位于.data段
}
初始化过程
编译器将初始值写入FLASH的 .data 镜像区。
单片机启动时,启动代码(如 startup_*.s)将FLASH中的初始值复制到RAM的 .data 段。
链接脚本片段
Ld
.data : {
_data_start = .; /* RAM中.data段的起始地址 */
*(.data*) /* 所有.data段内容 */
_data_end = .; /* RAM中.data段的结束地址 */
} > RAM AT > FLASH /* 物理存储在FLASH,运行时在RAM */
3. .bss 段(未初始化数据段)
定义与特性
代码示例
uint32_t bss_var; // 未初始化,位于.bss段
static char buffer[1024]; // 未初始化的静态数组,位于.bss段
int zero_var = 0; // 显式初始化为0,位于.bss段
初始化过程
启动代码将 .bss 段对应的RAM区域清零:
assembly
/* 典型启动代码片段(ARM汇编) */
ldr r0, =_bss_start
ldr r1, =_bss_end
mov r2, #0
clear_bss:
cmp r0, r1
strlt r2, [r0], #4
blt clear_bss
4. 堆(Heap)
定义与特性
代码示例
void demo_heap() {
int* ptr = (int*)malloc(100 * sizeof(int)); // 从堆分配
if (ptr != NULL) {
// 使用内存
free(ptr); // 必须手动释放
}
}
配置堆大小
在链接脚本中定义堆区域:
ld
_heap_start = .; /* 堆起始地址 */
. = . + 0x1000; /* 堆大小为4KB */
_heap_end = .; /* 堆结束地址 */
5. 栈(Stack)
定义与特性
代码示例
void demo_stack() {
int local_var = 42; // 局部变量,位于栈
char buffer[256]; // 大型局部数组,可能引发栈溢出
// ...
}
配置栈大小
在链接脚本中定义栈区域(通常位于RAM顶端):
Ld
_stack_top = ORIGIN(RAM) + LENGTH(RAM); /* 栈顶地址(向下生长) */
_stack_size = 0x800; /* 栈大小为2KB */
. = . - _stack_size; /* 分配栈空间 */
_stack_bottom = .; /* 栈底地址 */
6. 扩展段:.ccmram(紧耦合内存)
定义与特性
代码示例
1
__attribute__((section(".ccmram"))) uint32_t fast_buffer[128]; // 分配到CCM RAM
7. 内存布局验证(Map文件分析)
编译生成的 .map 文件显示各段地址和大小:
Plaintext
.text 0x08000000 0x4000 // 代码和常量(FLASH)
.data 0x20000000 0x200 // 已初始化变量(RAM)
.bss 0x20000200 0x400 // 未初始化变量(RAM)
.heap 0x20000600 0x1000 // 堆区域
.stack 0x20001600 0x800 // 栈区域
8. 实战注意事项
避免栈溢出:
优化RAM使用:
// 将大型只读数据放入FLASH
const uint8_t large_lookup_table[1024] __attribute__((section(".rodata"))) = { ... };
动态内存替代方案:
9. 链接脚本高级配置
通过自定义段实现精细控制:
ld
/* 将特定函数放入快速执行区域 */
.fast_code : {
*(.fast_code) /* 自定义段 */
} > FLASH_FAST
/* 将关键变量放入备份RAM(掉电保持) */
.backup_ram : {
_backup_start = .;
*(.backup_data) /* 自定义段 */
_backup_end = .;
} > BACKUP_RAM
通过深入理解内存段的划分,开发者可以优化资源使用,避免常见内存错误,并提升系统可靠性。建议结合具体芯片手册和调试工具(如STM32CubeIDE的Memory Analysis)实时验证内存分配。
2 .bss 段详细介绍
(Block Started by Symbol)
在单片机或嵌入式系统中,内存的 .bss 段(Block Started by Symbol)是一个关键内存区域,用于存储未初始化或显式初始化为零的全局变量和静态变量。它的核心目的是优化存储空间,并确保程序的正确初始化。以下是详细解释:
1. .bss 段的核心特性
特性 |
说明 |
存储内容 |
未初始化的全局变量、静态变量,或显式初始化为0的变量 |
存储介质 |
RAM(掉电丢失数据) |
初始化时机 |
程序启动时由启动代码(Bootloader)清零 |
空间优化 |
不占用FLASH空间(仅记录大小信息,无需存储初始值) |
2. .bss 段的作用
示例代码
// 未初始化的全局变量 → 位于.bss段
uint32_t global_uninitialized;
// 显式初始化为0 → 也存放在.bss段
uint32_t global_zero_initialized = 0;
void func() {
static int static_uninitialized; // 未初始化的静态变量 → .bss段
}
3. 为什么需要.bss段?
4. .bss 段的工作流程
步骤1:编译阶段
步骤2:链接阶段
步骤3:启动阶段
5. .bss 段与.data段的区别
对比项 |
.bss 段 |
.data 段 |
初始化方式 |
启动时清零 |
启动时从FLASH复制初始值到RAM |
FLASH占用 |
不存储初始值(仅记录大小) |
存储初始值 |
变量类型 |
未初始化或初始化为0的变量 |
已初始化的全局/静态变量 |
RAM内容初始化 |
全部清零 |
由FLASH中的初始值覆盖 |
6. 实际调试技巧
(1) 查看.bss段大小
.bss 0x20000000 0x400 // 地址和长度(单位:字节)
(2) 验证.bss段是否被清零
(3) 避免.bss段溢出
7. 注意事项
显式初始化为零的变量:
int x = 0; // 位于.bss段(编译器优化)
编译器会将其放入 .bss 段而非 .data 段,以减少FLASH占用。
未初始化的变量不等于随机值:
在嵌入式系统中,启动代码会清零 .bss 段,因此变量的初始值为 0,而非未定义值。
静态局部变量:
void func() {
static int y; // 未初始化的静态局部变量 → 位于.bss段
}
优化RAM使用:
将初始值为零的大数组放在 .bss 段而非 .data 段,避免占用FLASH空间:
uint8_t large_buffer[1024]; // 正确 → 位于.bss段
// 而非:
uint8_t large_buffer[1024] = {0}; // 错误 → 位于.data段(浪费FLASH)
3 全局变量的内存分段规则
变量类型 |
存储段 |
存储介质 |
生命周期 |
说明 |
已初始化的全局变量 |
.data段 |
RAM |
程序运行期间 |
初始值存储在FLASH中,启动时复制到RAM |
未初始化的全局变量 |
.bss段 |
RAM |
程序运行期间 |
启动时由启动代码清零(初始值为0) |
初始化为0的全局变量 |
.bss段 |
RAM |
程序运行期间 |
编译器优化后等同于未初始化变量 |
const修饰的全局变量 |
.rodata段 |
FLASH(ROM) |
程序生命周期内 |
只读,不可修改(存放于FLASH中) |
静态全局变量 |
.data或.bss |
RAM |
程序运行期间 |
静态全局变量遵循与普通全局变量相同的规则 |
3.1 示例代码与段分配
// 1. 已初始化的全局变量 -> .data段(RAM)
int global_initialized = 42;
// 2. 未初始化的全局变量 -> .bss段(RAM)
int global_uninitialized;
// 3. 显式初始化为0的全局变量 -> .bss段(RAM)
int global_zero_initialized = 0;
// 4. const修饰的全局变量 -> .rodata段(FLASH)
const int global_const = 100;
// 5. 静态全局变量 -> .data或.bss段(RAM)
static int static_global = 5; // .data段(已初始化)
static int static_global_uninit; // .bss段(未初始化)
详细说明
1. 已初始化的全局变量(.data段)
2. 未初始化的全局变量(.bss段)
3. const修饰的全局变量(.rodata段)
4. 静态全局变量
验证方法
1. 查看编译生成的Map文件
2. 调试器观察变量地址
特殊场景与注意事项
1. 初始化为0的变量优化
2. 静态局部变量
3. 多文件中的全局变量
总结
全局变量的存储段完全由初始化状态和修饰符决定:
合理规划全局变量的初始化方式,可显著优化FLASH和RAM的使用效率。
4 内存区域编译器划分
编译器将变量分配到不同内存段,具体取决于变量的类型、作用域和存储类别:
内存段 |
存储内容 |
生命周期 |
示例 |
.text |
可执行代码(机器指令) |
程序运行期间 |
void main() { ... } |
.data |
已初始化的全局变量、静态变量 |
程序运行期间 |
int global = 10; |
.bss |
未初始化或初始化为0的全局/静态变量 |
程序运行期间 |
int global_uninit; |
栈 (Stack) |
局部变量、函数参数 |
函数调用期间 |
int local_var = 5; |
堆 (Heap) |
动态分配的内存(malloc/new) |
显式释放前持续存在 |
int* ptr = malloc(100); |
.rodata |
只读常量(const修饰的全局变量) |
程序运行期间 |
const int PI = 3.14; |
2. 变量分配规则
(1) 全局变量
(2) 静态变量
(3) 局部变量
(4) 常量
3. 内存分配流程
步骤1:符号表生成
步骤2:内存段分配
步骤3:地址计算与对齐
步骤4:生成初始化数据
步骤5:栈与堆管理
4. 优化策略
(1) 寄存器分配
(2) 冗余变量消除
(3) 常量传播
5. 动态内存分配
动态内存(堆)由程序员手动管理,编译器生成调用内存管理函数的代码:
int* arr = (int*)malloc(10 * sizeof(int)); // 编译器生成调用malloc的指令
free(arr);
6. 实战验证方法
(1) 查看汇编代码
(2) 分析Map文件
(3) 调试器查看内存
总结
编译器通过类型、作用域和存储类别确定变量内存段,结合对齐和优化策略高效分配内存。理解这些机制有助于编写内存安全的代
5 .data 段大小的决定因素
示例计算
int a = 1; // 4字节 → .data段(RAM)
static double b = 2.0; // 8字节 → .data段(RAM)
const char c = 'A'; // 1字节 → .rodata段(Flash,不计入.data)
2. 如何控制 .data 段大小?
(1) 减少已初始化的全局/静态变量
(2) 链接脚本(Linker Script)配置
在链接脚本中显式定义 .data 段的地址和空间大小,防止溢出:
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K /* Flash配置 */
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K /* RAM配置 */
}
SECTIONS {
.data : {
_data_start = .;
*(.data*) /* 所有.data段内容 */
_data_end = .;
} > RAM AT > FLASH /* .data段占用的RAM空间根据变量实际大小动态分配 */
}
(3) 结构体对齐优化
3. 验证 .data 段大小
(1) 生成Map文件
在编译选项中添加 -Wl,-Map=output.map,生成的 output.map 文件会显示各段大小:
.data 0x20000000 0x18 /* 0x18 = 24字节 */
global_var 0x20000000 0x4
static_var 0x20000004 0x8
...
(2) 使用 size 命令
查看可执行文件各段大小:
arm-none-eabi-size firmware.elf
输出示例:
text data bss dec hex filename
12345 678 901 13924 3664 firmware.elf
(3) 调试器查看符号地址
使用调试工具(如GDB)检查变量的地址范围:
(gdb) p &global_var # 输出 0x20000000(RAM地址)
(gdb) p &static_var # 输出 0x20000004
4. 解决 .data 段溢出的问题
如果 .data 段超出RAM容量:
5. 实战示例
场景:某STM32工程 .data 段过大,需优化。
步骤1:识别占用大的变量
int buffer[1024] = {0}; // 初始化为0 → 实际在.bss段(无需优化)
const char* strings[] = {"Hello", "World"}; // 指针数组在.data段,字符串在.rodata段
步骤2:优化代码 将常量指针改为 const,直接指向 .rodata:
const char* const strings[] = {"Hello", "World"}; // 指针本身移至.rodata
步骤3:重新编译并检查Map文件
.data 0x20000000 0x8 /* 优化后大小减少 */
6 .text 段存储与执行
在嵌入式系统中,.text 段存储编译后的 可执行代码(机器指令),其物理位置位于 ROM非易失性存储器(通常是 Flash) 中。以下是详细说明:
1. .text 段的内容
示例代码
// main函数 → 编译后代码存入.text段
int main() {
while(1) {
// ...
}
}
// 中断服务程序 → 代码存入.text段
void SysTick_Handler(void) {
// ...
}
2. .text 段的存储介质
(1) Flash(非易失性存储器)
(2) 特殊情况:RAM 中执行代码
3. 为什么代码必须存储在 Flash 中?
非易失性需求:
代码需要长期保存,断电后不丢失。
空间容量:
Flash 容量通常远大于 RAM(如 STM32F4 的 Flash 可达 2MB,RAM 仅 192KB)。
安全性:
防止程序运行时意外修改自身代码。
4. .text 段的加载与执行流程
烧录阶段:
编译器将 .text 段代码写入 Flash(通过烧录工具如 ST-Link、J-Link)。
启动阶段:
MCU 上电后,从 Flash 的复位向量(Reset Vector)地址(如 0x08000004)读取指令,开始执行代码。
运行时:
CPU 直接从 Flash 读取指令执行,或通过缓存加速。
5. 验证 .text 段地址的方法
(1) 查看编译生成的 Map 文件
在编译选项中添加 -Wl,-Map=output.map,生成的文件中会显示 .text 段地址:
.text 0x08000000 0x4000 /* 起始地址和大小 */
(2) 调试器查看函数地址
使用 GDB 或 IDE 调试工具(如 Keil、STM32CubeIDE)查看函数地址:
(gdb) p main # 输出类似 0x08000123(位于Flash地址范围内)
(3) 反汇编代码
通过 objdump 生成反汇编文件:
arm-none-eabi-objdump -d firmware.elf > disassembly.txt
查看反汇编结果:
08000123 <main>:
8000123: b480 push {r7}
8000125: af00 add r7, sp, #0
8000127: e7fe b.n 8000127 <main+0x4>
6. 优化 .text 段大小的技巧
编译器优化选项:
避免冗余代码:
手动指定代码段:
高频代码放入 .ram_code 段,减少 Flash 访问次数。
总结
- 代码从 Flash 直接执行,可通过缓存或 RAM 加速关键路径。
- 通过 Map 文件、调试工具和反汇编验证代码位置,结合编译选项优化空间占用。
- .text 段存储程序的机器指令,位于 Flash 中,地址范围由芯片决定(如 0x08000000)。
- 禁用未使用的中断处理函数(Weak 函数需覆盖)。
- 减少库函数链接(如替换 printf 为轻型实现)。
- -Os(优化代码大小):
arm-none-eabi-gcc -Os -o firmware.elf main.c - -ffunction-sections & -Wl,--gc-sections:
移除未使用的函数。 - 目的:提升高频执行代码的性能(如实时信号处理算法)。
- 实现方式:
- 手动将代码从 Flash 复制到 RAM。
- 通过链接脚本将特定函数分配到 RAM 段。
- 示例(链接脚本):
.ram_code : {
*(.ram_code) /* 将.ram_code段分配到RAM */
} > RAM AT > FLASH /* 初始值在Flash,启动时复制到RAM */ - 代码注解:
__attribute__((section(".ram_code"))) void fast_function() {
// 高频执行代码(从RAM运行)
} - 特性:
- 只读(Read-Only):运行时不可修改。
- 掉电数据不丢失。
- 读取速度较慢(相比 RAM),但现代 MCU 通常通过指令缓存(如 ARM Cortex-M7 的 I-Cache)加速。
- 地址范围:
- 典型 Cortex-M 设备的 Flash 起始地址为 0x08000000。
- 示例:STM32F4 的 Flash 地址范围为 0x08000000 ~ 0x081FFFFF(2MB)。
- 机器指令:函数、中断服务程序(ISR)、启动代码等。
- 只读数据(部分编译器可能纳入 .text 或 .rodata):
- 中断向量表(如 ARM Cortex-M 的向量表)。
- 编译器生成的常量跳转表(如 switch-case 语句的跳转表)。
- 优化核心:减少初始化数据、利用.bss和.rodata段、调整内存对齐。
- 通过Map文件和调试工具验证优化效果,确保RAM和Flash资源合理分配。X
- .data段大小由已初始化的全局/静态变量决定,需在代码和链接脚本中协同优化。
- strings 的指针地址在 .data 段,字符串内容在 .rodata 段。
- 优化代码:减少全局/静态变量数量或大小。
- 调整内存分配:在链接脚本中扩展RAM空间或优化其他段(如.bss)。
- 使用内存覆盖:将部分数据存储在Flash中,运行时按需加载(牺牲速度换空间)。
- data 列表示 .data 段大小(单位字节)。
- 使用 #pragma pack 减少结构体内存空洞:
#pragma pack(1) // 1字节对齐
struct SensorData {
uint8_t id; // 1字节
uint32_t value; // 4字节
}; // 总大小=5字节(默认对齐时为8字节)
#pragma pack() // 恢复默认对齐 - 关键点:.data 段的RAM占用由变量实际大小决定,Flash镜像大小与之相同。
- 优化策略:
- 将变量改为 未初始化(移至 .bss 段):
int a; // 从 .data 改为 .bss(初始值0,不占用Flash) - 使用 const 修饰符将常量放入 .rodata 段:
const int table[100] = { ... }; // 移入Flash的.rodata段 - RAM占用:a + b = 4 + 8 = 12 字节(.data段)。
- Flash占用:.data段的初始值镜像占用同样 12 字节。
- 已初始化的全局变量:例如 int global = 10;。
- 已初始化的静态变量:例如 static float static_var = 3.14;。
- 初始值存储开销:每个变量的初始值会占用 Flash空间(启动时需复制到RAM)。
- 使用GDB或IDE调试工具,直接查看变量地址:
(gdb) p &global_var # 输出类似 0x20000000(RAM地址)
(gdb) p &const_var # 输出类似 0x08001000(Flash地址) - 链接生成的Map文件(-Wl,-Map=output.map)显示各段地址和变量布局:
.text 0x08000000 0x200
.data 0x20000000 0x10
.bss 0x20000010 0x8 - GCC生成汇编代码:
gcc -S main.c -o main.s - 观察变量地址分配:
.data
global_var: .word 10 # .data段
.bss
static_var: .space 4 # .bss段
.text
main:
pushl %ebp
movl %esp, %ebp
subl $16, %esp # 栈分配16字节(含局部变量) - 直接替换常量值到使用位置:
const int N = 100;
int arr[N]; // 替换为 int arr[100]; - 删除未使用的变量:
int unused_var = 10; // 被优化删除 - 高频使用的局部变量优先存入寄存器(减少内存访问)。
register int i; // 建议编译器使用寄存器(实际由编译器决定) - 栈分配:编译器生成代码,通过移动栈指针(SP)分配局部变量。
x86示例:分配8字节栈空间
sub esp, 8 - 堆分配:向操作系统请求内存(如malloc调用brk或mmap)。
- 已初始化的全局/静态变量,编译器将初始值写入Flash的.data镜像区。
int initialized = 0x1234; // 初始值0x1234存储在Flash,启动时复制到RAM - 填充 (Padding):插入空字节避免访问错误。
struct Example {
char a; // 1字节
// 填充3字节
int b; // 4字节(地址为4的倍数)
}; // 总大小 = 8字节 - char:1字节对齐
- int:4字节对齐
- double:8字节对齐
- 计算变量在段内的偏移地址,满足内存对齐要求。
- 对齐规则:
- 根据变量属性确定内存段:
int global = 10; // .data段
static float static_var; // .bss段
void func() {
int local; // 栈
} - 编译器解析代码,记录变量类型、作用域和存储类别。
- 示例:static int x; → 符号表标记为静态变量,存入.bss段。
- 字符串字面量 → .rodata段(如 char* str = "Hello";)
- const全局变量 → .rodata段(Flash,只读)
- 普通局部变量 → 栈内存(函数调用时自动分配)
- 寄存器变量 → 可能被优化到CPU寄存器(register int i;)
- 静态全局变量 → 类似全局变量(.data或.bss)
- 静态局部变量 → .data段(已初始化)或.bss段(未初始化)
- 已初始化 → .data段(RAM,初始值存储在Flash)
- 未初始化 → .bss段(RAM,启动时清零)
- .data段:初始化为非零值的全局变量。
- .bss段:未初始化或初始化为零的全局变量。
- .rodata段:const 修饰的全局常量。
- 跨文件的全局变量需使用 extern 声明:
// file1.c
int global_var = 42; // 定义在.data段
// file2.c
extern int global_var; // 声明使用其他文件中定义的全局变量 - 静态局部变量与静态全局变量的分配规则一致:
void func() {
static int static_local = 10; // 已初始化 -> .data段
static int static_local_uninit; // 未初始化 -> .bss段
} - 显式初始化为 0 的变量会被编译器优化到 .bss 段,但需验证编译器行为:
int x = 0; // 可能被优化到.bss段 - .data 段变量地址在RAM范围内(如 0x2000xxxx)。
- .rodata 段变量地址在FLASH范围内(如 0x0800xxxx)。
- 通过调试工具(如Keil、IAR或GDB)查看变量地址:
- 在编译选项中添加 -Wl,-Map=output.map,生成的 output.map 文件会显示全局变量的段地址:
.data 0x20000000 0x8 // 已初始化全局变量
global_initialized 0x20000000 0x4
static_global 0x20000004 0x4
.bss 0x20000008 0x8 // 未初始化全局变量
global_uninitialized 0x20000008 0x4
static_global_uninit 0x2000000c 0x4
.rodata 0x08001000 0x4 // 只读常量
global_const 0x08001000 0x4 - 作用域:仅在定义它的文件内可见(与普通全局变量的全局作用域不同)。
- 存储规则:与普通全局变量完全相同,根据初始化状态分配在 .data 或 .bss 段。
- 特性:只读,存储在FLASH中,无法修改。
- 应用场景:常量表、配置文件等无需修改的数据。
- 访问限制:
const int READ_ONLY_DATA = 0x1234;
// READ_ONLY_DATA = 0x5678; // 编译报错:尝试修改只读数据 - 定义:未显式初始化,或显式初始化为 0。
- 存储方式:
- 不占用FLASH空间,仅在RAM中分配区域。
- 启动代码将 .bss 段对应的RAM区域清零。
- 编译器优化:
- 初始化为 0 的变量会被编译器优化到 .bss 段,减少FLASH占用。
- 示例:int x = 0; 实际存储在 .bss 段而非 .data 段。
- 定义:在声明时被赋予非零初始值。
- 存储方式:
- 初始值存储在FLASH的 .data 镜像区。
- 程序启动时,启动代码将FLASH中的初始值复制到RAM的 .data 段。
- 链接脚本配置:
.data : {
_data_start = .; /* RAM中的.data起始地址 */
*(.data*) /* 所有已初始化的全局变量 */
_data_end = .;
} > RAM AT > FLASH /* 物理存储在FLASH,运行时在RAM */ - 禁止未初始化变量:通过编译器选项(如 -Werror=uninitialized)强制变量必须显式初始化。
- 静态分析工具:使用工具检查未初始化变量的使用。
- 初始化为 NULL(即 0)的指针位于 .bss 段:
int* ptr = NULL; // 位于.bss段 - 可以,通过编译器属性强制指定变量到 .bss 段:
uint32_t my_var __attribute__((section(".bss"))); - 在链接脚本中检查RAM剩余空间是否足够容纳 .bss 段:
Memory region Used Size Free Size
RAM 0x400 0x1000 // 已用1KB,剩余4KB - 在调试器中,观察 .bss 段变量的初始值是否为零:
extern uint32_t global_uninitialized;
// 调试时检查 global_uninitialized == 0 - 通过编译生成的 Map文件(如 output.map)查看 .bss 段地址和大小:
- 单片机上电后,启动代码(Startup Code)清零 .bss 段。
示例汇编代码(ARM Cortex-M):
ldr r0, =_bss_start /* 加载.bss段起始地址 */
ldr r1, =_bss_end /* 加载.bss段结束地址 */
mov r2, #0 /* 清零值 */
clear_bss_loop:
cmp r0, r1 /* 检查是否到达结束地址 */
strlt r2, [r0], #4 /* 清零内存并递增指针 */
blt clear_bss_loop /* 循环直到完成 */ - 链接脚本(Linker Script)定义 .bss 段在RAM中的位置。
示例链接脚本片段:
ld
.bss : {
_bss_start = .; /* 记录.bss段起始地址 */
*(.bss*) /* 所有.bss段内容 */
_bss_end = .; /* 记录.bss段结束地址 */
} > RAM - 编译器识别未初始化的全局/静态变量,标记为 .bss 段。
- 在生成的符号表中记录 .bss 段的起始地址和大小。
- 减少二进制文件体积:
如果未初始化的变量放在 .data 段,需要在FLASH中存储零值,浪费空间。
.bss 段只需记录长度信息,无需存储实际数据。 - 提高启动效率:
启动时只需将 .bss 段对应的RAM区域清零,而不是逐个复制初始值(如 .data 段)。 - 节省FLASH空间:
未初始化的变量不需要在FLASH中存储初始值,仅在运行时分配RAM空间并初始化为零。 - 统一初始化逻辑:
所有未初始化或零初始化的变量统一管理,避免分散处理。 - 使用内存池或对象池替代 malloc,减少碎片。
- 例:静态预分配缓冲区管理:
c
#define POOL_SIZE 10
static uint8_t memory_pool[POOL_SIZE][256]; // 预分配内存池
static bool pool_used[POOL_SIZE] = {0};
void* my_alloc() {
for (int i = 0; i < POOL_SIZE; i++) {
if (!pool_used[i]) {
pool_used[i] = true;
return memory_pool[i];
}
}
return NULL; // 分配失败
} - 使用静态分析工具(如 -Wstack-usage)监控栈深度。
- 避免在函数内定义大型局部数组,改用堆或全局数组。
- 适用平台:ARM Cortex-M4/M7等高阶MCU。
- 存储内容:高频访问数据(如DMA缓冲区或实时任务数据)。
- 优势:独立总线访问,可与CPU指令执行并行。
- 存储内容:局部变量、函数调用时的返回地址、寄存器保存和参数传递。
- 存储介质:RAM,自动分配和释放。
- 生命周期:函数调用期间存在。
- 风险:栈溢出(如递归过深或大型局部数组)。
- 存储内容:动态分配的内存(如 malloc/free 管理)。
- 存储介质:RAM,手动分配和释放。
- 生命周期:从 malloc 到 free 期间有效。
- 风险:内存碎片、泄漏、溢出。
- 存储内容:未初始化或显式初始化为0的全局变量、静态变量。
- 存储介质:RAM,启动时由启动代码清零。
- 生命周期:程序运行期间持续存在。
- 优势:节省FLASH空间,无需存储初始值。
- 存储内容:已初始化的全局变量、静态变量。
- 存储介质:RAM(运行时),但其初始值存储在FLASH中,启动时由启动代码复制到RAM。
- 生命周期:程序运行期间持续存在。
- 只读属性:尝试修改 .text 段内容会导致硬件错误(如ARM的 HardFault)。
- 优化技巧:频繁调用的函数可使用 __attribute__((section(".fast_code"))) 分配到快速FLASH区域(如果支持)。
- 存储内容:编译后的机器指令(代码)和只读常量(如 const 变量)。
- 存储介质:FLASH(ROM),掉电不丢失。
- 生命周期:程序生命周期内始终存在,不可修改。