当前位置: 首页 > news >正文

Keil中内存概念:Flash、SRAM、RO、RW、ZI、.data、.bss、heap、stack、MAP文件

此文章转载于微信公众号:嵌入式电子学习,只作为笔记备忘录使用

内存属性

理解Keil MDK(或ARM编译器)中关于程序内存布局的一些基本概念(RO、RW、ZI和.data、.bss、heap、stack、Flash、SRAM)。这些概念对于理解程序如何被加载和运行,以及如何优化内存使用至关重要。

1. 基础概念详解

1.1 存储介质分类

Flash(非易失性存储)

  • • 特点:掉电数据不丢失,读取速度快,写入速度慢

  • • 存储内容:程序代码、常量数据、初始化数据

  • • 访问方式:直接读取,需要通过特定接口编程

RAM(易失性存储)

  • • 特点:掉电数据丢失,读写速度快

  • • 存储内容:变量、堆栈、运行时数据

  • • 访问方式:直接读写

1.2 程序段分类

RO(Read Only)段
  • 存储位置:Flash

  • 包含内容

    • • 程序代码(.text段)

    • • 只读数据(.rodata段)

    • • 常量字符串、const变量

  • 特点:运行时不可修改

RW(Read Write)段
  • 存储位置:Flash中存初始值,RAM中存运行时值

  • 包含内容:已初始化且非零的全局/静态变量

  • 特点:启动时需要从Flash复制到RAM

ZI(Zero Initialized)段
  • 存储位置:RAM

  • 包含内容:未初始化或显式初始化为0的全局/静态变量

  • 特点:启动时清零初始化

1.3 常见段名与内存区域

  • .text:表示代码段Code,存放在Flash中。

  • .constdata 或 .rodata:只读数据段RO,存放在Flash中。

  • .data:已初始化的全局变量和静态变量(RW数据),在Flash中保存初始值,在RAM中存放运行时值。

  • .bss:未初始化的全局变量和静态变量(ZI数据),在RAM中,程序启动时初始化为0,堆和栈属于.bss。

  • • 栈(stack):用于局部变量、函数调用等,由编译器自动管理,通常从RAM的高地址向低地址增长。

  • • 堆(heap):用于动态内存分配,由程序员管理(malloc/free),通常从RAM的低地址向高地址增长。

1.4 加载域和执行域

1、加载区域表示代码和数据下载到芯片时存储到哪段地址,可以存储到片上Flash,也可以存储到片外Flash,也可以存储到RAM

  • • 对于代码,为只读类型,运行时无法更改,因此存储在Flash中即加载区域是Flash地址段。

  • • 对于数据,其分成几类:

    • • 对于RO只读数据,比如const类型、字符串等等,其存储在Flash,因此加载区域也是Flash地址段;

    • • 对于RW读写数据,比如.data,如果其有初值,那么初值要存放在Flash中,运行时先从Flash中取出初值对RW数据进行赋值,然后运行时RW数据的访问地址是在RAM里,也就是RW数据的加载区域是Flash地址段,执行区域是RAM地址段;

    • • 对于ZI数据,比如.bss和stack、heap,表示初始化为零的全局变量,因此无需在Flash中存放初值,也就无所谓加载区域,只有执行区域,执行区域也就是程序运行时,如果要访问这个变量,要去哪个地址段寻找。

2、执行区域表示上电运行后程序和数据从哪个地址开始执行或访问

  • • 对于代码:也就是从哪个地址开始读取代码语句并执行,一般是程序存储在哪里,就从哪里执行,代码的执行区域和加载区域保持一致。

  • • 对于数据:表示程序运行起来后,去哪个地址可以访问数据。

    • • 对于RO数据,例如const,需要存储在Flash中,因此其加载区域地址就处在Flash中,程序运行起来后也是去Flash地址段访问const变量,因此其执行区域也是Flash地址段,两个区域保持一致

    • • 对于RW数据,如果初值不为零,那么初值需要存储到Flash中(即使初值为零,加载区域似乎也是Flash段),则其加载区域是Flash地址段,运行时访问RW数据则要去RAM里,因此执行区域是RAM地址段。

参考文章:内存分配基础(2):简单例子

2. 内存区域详细对应关系

2.1 编译时段的映射

2.2 详细对应表

内存区域

对应段

存储介质

初始化方式

内容示例

.text

RO

Flash

编译时确定

函数代码、中断向量表

.rodata

RO

Flash

编译时确定

const常量、字符串常量

.data

RW

Flash+RAM

启动时从Flash复制

int a = 100;

.bss

ZI

RAM

启动时清零

int b; 或 int c = 0;

heap

ZI(动态)

RAM

运行时分配

malloc()分配的内存

stack

ZI(动态)

RAM

运行时压栈

局部变量、函数参数

3. 启动过程分析

系统上电后,首先从Flash中读取代码和数据进行初始化。具体步骤:

  1. 1. 初始化栈指针(SP)和程序计数器(PC)。

  2. 2. 将RW数据从Flash中复制到RAM中(这部分数据在Flash中紧跟在RO数据之后)。

  3. 3. 将ZI数据所在的RAM区域全部清零。

  4. 4. 跳转到main函数执行。

参考文章:上电启动(3):从复位到main()的启动文件详解(万字长文整理)

4. Map文件解析

Map文件展示了程序的内存布局,包括各个段的大小、地址分配等。通过Map文件,我们可以查看:

  • • 代码段、RO数据段、RW数据段、ZI数据段的大小和位置。

  • • 各个模块(源文件)占用的代码和数据空间。

4.1 Map文件分析

1. 模块摘要 Module Summary: Code (inc. data) RO Data RW Data ZI Data Debug Object Name 1200 200 400 100 500 8000 main.o 800 150 200 50 300 6000 library.o

可以看到用户每个源文件所占据内存大小,inc表示内联函数和数据。

2. 总内存占用 Total RO Size (Code + RO Data) 1600 ( 1.56kB) Total RW Size (RW Data + ZI Data) 900 ( 0.88kB) Total ROM Size (Code + RO Data + RW Data) 1700 ( 1.66kB)

汇总看到整个工程所占据的Flash和SRAM空间。

3. 内存区域分布 Memory Map of the image: Flash区域 Load Region LR_FLASH (Base: 0x08000000, Size: 0x00000800, Max: 0x00080000) Execution Region ER_FLASH (Base: 0x08000000, Size: 0x00000650) Base Addr Size Type Attr Idx E Section Name Object 0x08000000 0x00000200 Code RO 1 .text startup_stm32f10x.o 0x08000200 0x00000400 Data RO 2 .constdata main.o RAM区域 Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x00000400) Base Addr Size Type Attr Idx E Section Name Object 0x20000000 0x00000100 Data RW 10 .data main.o 0x20000100 0x00000200 Zero RW 11 .bss main.o 0x20000300 0x00000100 Zero RW 12 heap .o

可以看到Flash和SRAM中具体的每一段地址存放了哪些数据和代码。

4.2 关键指标解读

编译信息解读

Program Size: Code=xxxx RO-data=xxxx RW-data=xxxx ZI-data=xxxx
  • Code: 实际代码大小,存储在Flash中

  • RO-data: 只读数据大小,存储在Flash中

  • RW-data: 已初始化的读写数据大小,在Flash中存储初始值,在RAM中占用相同大小的空间

  • ZI-data: 零初始化数据大小,在RAM中占用空间,但不在Flash中占用空间(除了初始化为0的说明信息,但不占用实际数据空间)

重要计算公式

Flash占用 = Code + RO Data + RW Data的初始值 RAM占用 = RW Data + ZI Data + Stack + Heap

注意:RW数据在Flash和RAM中各有一份,Flash中存储的是初始值,RAM中是运行时的值。

5. 内存优化

5.1 常见优化方法

通过理解这些概念,我们可以有针对性地优化程序:

  • • 减少全局变量的使用,特别是已初始化的全局变量(RW数据)和未初始化的全局变量(ZI数据),可以节省RAM空间。

  • • 将常量数据尽量使用const关键字定义为只读数据,使其存储在Flash中,而不是RAM中。

  • • 优化代码大小,减少Flash占用。

  • • 合理设置堆栈大小,避免溢出。

5.2 优化建议说明

int global_var = 100;
  • • 定义一个全局变量,带有非零初始值,属于RW数据,在Flash中存储初始值100,在RAM中有一个变量占4字节。

int global_var2;
  • • 定义一个全局变量,零初始值,属于ZI数据,在RAM中占4字节,启动时被初始化为0。

const int global_const = 200;
  • • 定义一个const数据,属于RO数据,存储在Flash中,不占用RAM

  • • 堆和栈的大小通常由启动文件(startup.s)中的设置决定,在Map文件中可以查看它们的地址范围。

查看MAP文件

  • • 查看各个模块的代码和数据占用,找出占用较大的模块,进行优化。

  • • 检查RW和ZI数据的大小,优化全局变量和静态变量的使用。

  • • 确认堆栈大小是否足够,避免堆栈溢出。

优化方向

  • • 如果Flash紧张,可以优化代码和常量数据,例如使用更高效的算法,减少常量数据(如字符串、数组)等。

  • • 如果RAM紧张,可以减少全局变量和静态变量,使用局部变量(栈上分配),减少动态内存分配(堆)等。

  • • 注意:栈和堆的增长方向以及边界检查很重要,如果堆和栈发生重叠,会导致程序崩溃。因此,需要合理设置堆栈大小,并可能使用内存保护功能。在代码中监控堆栈使用。

/*********************************************************************************************************************** * Function Name: StackFillMagic * Description : 初始化阶段调用一次将栈区全部填充幻数 * Arguments : None * Return Value : None ***********************************************************************************************************************/ void StackFillMagic(void) { uint32_t* base = &__base_sp; //栈顶边界 uint32_t* top = (uint32_t*)__get_MSP(); //这里要使用当前栈指针 while(base < top) { * base++ = 0xDEADBEEF; //填充幻数 } }
/*********************************************************************************************************************** * Function Name: CheckStackOverflow * Description : 程序运行过程中一直调用此函数检测栈空间使用是否溢出 * Arguments : None * Return Value : None ***********************************************************************************************************************/ uint16_t CheckStackOverflow(void) { uint16_t use_size = 0; uint32_t* base = &__base_sp; //栈顶边界 while(*base == 0xDEADBEEF && base < (&__initial_sp)) { base++; //检查哪些地方的数据不是幻数,表示此区域已经使用了 } use_size = (base - (&__base_sp))*sizeof(uint32_t); //字节个数 return (use_size); }

5.3 优化检查项

  • • 检查全局变量是否必要,能否改为局部变量

  • • 常量数据使用const修饰,确保存储在Flash

  • • 大数组考虑使用动态分配或放在特定内存区域

  • • 定期检查堆栈使用情况,避免溢出

  • • 使用合适的编译优化选项(-Os, -O2等)

  • • 分析Map文件,找出内存占用大的模块

http://www.jsqmd.com/news/586744/

相关文章:

  • 用干词背单词,30天轻松背完小学词库1200单词!
  • 告别EFI配置噩梦:OpCore-Simplify如何重新定义Hackintosh体验
  • 如何彻底解决Windows自动休眠问题?MouseJiggler全场景应用指南
  • MySQL的每一行数据永远都有三个隐藏字段吗?
  • 2026年4月克拉管品牌怎么选择,抗疲劳特性,克拉管长期使用佳 - 品牌推荐师
  • 【CSDN重磅】50+维度董事长智能建模系统:基于OpenCV的领导者数字孪生实战
  • tcc-g15:Dell G15笔记本的智能散热调控与全场景适配方案
  • 猫抓:网页资源下载终极解决方案,让媒体获取从未如此简单
  • 2026六安汽车贴膜第三方横向评测:四大官方授权门店深度对比 - GrowthUME
  • 第七章 技术栈全景:支撑千万级工业互联网平台的技术选型考量
  • 基于计算机视觉、利用NVIDIATAO工具包与YOLOv8实现印度智慧城市场景下骑行人员未佩戴头盔违规检测与车辆识别
  • 让旧款Mac焕发新生:OpenCore Legacy Patcher完全指南
  • 突破网盘下载瓶颈:开源工具如何重塑你的文件获取体验
  • 多显示器壁纸终极解决方案:Superpaper 完整指南
  • 5分钟掌握Label Studio ML Backend:打造企业级AI标注自动化系统
  • 【AI Engineering】身体已成功 Handshake 回家,内网 Agent 仍在 504 Timeout 里闭门思过!
  • AI教材编写秘籍:掌握这些方法,用AI写出低查重率的优质教材!
  • AI Coding与单元测试的协同进化:从验证到驱动
  • 1013 Battle Over Cities(比较好的一题)
  • 二分边界防止循环
  • 探寻ROHS2.0检测仪适合哪些行业使用,生产商哪家靠谱 - myqiye
  • 终极Dlib预编译包指南:高效解决Windows环境安装难题
  • STC15F2K60S2单片机最小系统板DIY指南:从选件到焊接,一次点亮
  • 杭州高端腕表鉴定真假全攻略:30+奢华品牌防伪解析、地域案例与6城服务对比 - 时光修表匠
  • 分析rohs2.0检测仪厂商哪家好,分享价格区间和品牌推荐 - mypinpai
  • B站Windows客户端高效解决方案:告别浏览器困扰,打造专业视频体验平台
  • 秒传技术突破:如何让文件分享效率提升10倍的底层逻辑与实践指南
  • 猫抓资源嗅探插件:三步搞定网页视频下载的完整指南
  • 消息队列发送消息场景分析
  • 【C++】muduo接口补充