RT-Thread版本:4.0.5
MCU型号:STM32F103RCT6(ARM Cortex-M3 内核)
1 各段含义
一般 MCU 包含的存储空间有:片内 Flash (ROM)与片内 RAM。
Total RO Size (Code + RO Data)
Total RW Size (RW Data + ZI Data) >>> RAM
Total ROM Size (Code + RO Data + RW Data) >>> Flash(ROM)
Section | 含义 | 内存地址分配阶段 | 属性 |
---|---|---|---|
Code | 存放函数体的二进制代码 | 编译时分配 | 只读 |
RO-data(Read Only data) | 常量(局部常量在栈区) | 编译时分配 | 只读 |
RW-data(Read Write data) | 初值非0的全局变量和静态变量 | 编译时分配 | 读写 |
ZI-data(Zero Initialie data) | 未初始化或初始化为0的全局变量和静态变量 | 编译时分配 | 读写 |
ZI-data栈空间(Stack) | 局部变量(局部静态变量除外) | 运行时分配 | 读写 |
ZI-data堆空间(Heap) | 使用malloc动态分配的空间 | 运行时分配 | 读写 |
MDK的map
文件可以查看RO段、RW段和部分ZI段的变量与函数的起始地址、大小等,ZI段的堆栈区中的局部变量是无法查看的,因为它们的内存地址是运行时分配的。
2 测试代码
struct test{
int a;
char b;
short c;
const char* p;
};
volatile const char* s1 = "hello world"; // 常量 RO
volatile int g_buf1[100] = {1}; // 初值非0的全局数组 RW
volatile char g_buf2[9]; // 初值为0的全局数组 ZI (bss)
volatile char g_buf3[8]; // 初值为0的全局数组 ZI (data)
volatile int g_a1 = 1; // 初值非0的全局变量 RW
volatile char g_a2; // 未初始化的全局变量 ZI
volatile static int s_a1 = 2; // 初始化的全局静态变量 RW
volatile static int s_a2; // 未初始化的全局静态变量 ZI
volatile int* g_ptr1; // 未初始化的全局指针变量 ZI
struct test ss_t; // 未初始化的全局结构体变量 ZI (bss)
// int *ptr1 = rt_malloc(4 * sizeof(int)); // 直接报错
int main(void) // Code
{
volatile static int s_a3[3] = {0}; // 初始化为0的局部静态数组 ZI (bss)
volatile static int s_a4[4] = {1}; // 初始化的局部静态数组 RW (data)
volatile static int s_a5; // 未初始化的局部静态变量 ZI
volatile static int s_a6 = 2; // 初始化的局部静态变量 RW
const char c1 = 't'; // 局部常量 ZI-Stack
volatile int a1; // 局部变量 ZI-Stack
volatile int *ptr2 = rt_malloc(4 * sizeof(int)); // 局部变量 ZI-heap, ptr2指针指向的内存空间位于动态内存堆空间中
// bss段变量必须至少要有1个被调用,才能显示
g_buf2[0] = 3;
/* 局部静态变量只有被调用才能在map文件查看到 */
rt_kprintf("%d, %d, %d, %d", s_a3[0], s_a4, s_a5, s_a6);
return 0;
}
加volatile
关键字防止未使用的变量被编译器优化。
map
文件相关内容如下:
data段:
全局变量和常量:
s1 0x20000000 Data 4 main.o(.data)
g_buf1 0x20000004 Data 400 main.o(.data)
g_buf3 0x20000194 Data 8 main.o(.data)
g_a1 0x2000019c Data 4 main.o(.data)
g_a2 0x200001a0 Data 1 main.o(.data)
g_ptr1 0x200001ac Data 4 main.o(.data)
静态变量:
s_a1 0x200001a4 Data 4 main.o(.data)
s_a2 0x200001a8 Data 4 main.o(.data)
s_a4 0x200001b0 Data 16 main.o(.data)
s_a5 0x200001c0 Data 4 main.o(.data)
s_a6 0x200001c4 Data 4 main.o(.data)
bss段(mdk优化,大于8字节才进bss):
未初始化的全局变量:
g_buf2 0x200004c4 Data 9 main.o(.bss)
ss_t 0x200004d0 Data 12 main.o(.bss)
未初始化的静态变量:
s_a3 0x200004dc Data 12 main.o(.bss)
堆栈变量无法查看,运行时才分配内存。
关于text、data、bss说明:
- text:存放程序执行代码,只读。
- data:存放已初始化的全局变量和静态变量、全局常量
- bss:即ZI-data(不含堆栈),存放未初始化或初始化为0的全局变量和静态变量
注意:
- Keil对程序进行了优化,未初始化为初始化为0的全局变量和静态变量(结构体、数组)只有内存大于8字节时才会存放在bss段,否则存放在data段
官方给出的解释:ARMCC: Variables Get Located To RW Rather Than ZI
可以在Options for Target - C/C++ - Misc Controls通过--bss_threshold=n
(n为bss段优化阈值)修改。 - 测试函数要调用外部变量或函数,不然函数会被编译器优化,这样即使用
volatile
关键字定义的变量也无法在map
文件中查看。 - 当前.C文件中bss段和data段变量分别必须至少要有1个被调用,不然相应段中变量都会被编译器优化。
- 局部静态变量只有被调用才不会被优化。
3 可执行映像文件启动搬运流程
可执行映像文件(烧录文件)的内存分布如下图(图左)所示,主要包含RO段和RW段部分:其中 RO 段中保存了 Code、RO-data 的数据,RW 段保存了 RW-data 的数据,由于 ZI-data 都是 0,所以未包含在映像文件中。
STM32 在上电启动之后默认从 Flash 启动,启动之后会将 RW 段中的 RW-data(初始化的全局变量)搬运到 RAM 中,但不会搬运 RO 段,即 CPU 的执行代码从 Flash 中读取,另外根据编译器给出的 ZI 段的起始地址和大小分配出 ZI 段,并将这块 RAM 区域清零(由用户程序或编译器提供的一些库函数初始化成零,这样做节省ROM空间)。
RTT的动态内存堆即为未使用的 RAM 空间,应用程序申请和释放的内存块都来自该空间。
END