您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center 汽车系统工程   模型库  
会员   
   
LLM大模型与智能体开发实战
2026年1月17、24日 北京+线上
需求分析与管理
2026年1月22-23日 北京+线上
UAF与企业架构
2026年2月3-4日 北京+线上
     
   
 订阅
Keil中内存概念:Flash、SRAM、RO、RW、ZI、.data、.bss、heap、stack、MAP文件
 
作者:嵌入式电子学习
  103   次浏览      6 次
 2025-12-18
 
编辑推荐:
本文介绍了Keil MDK(或ARM编译器)中关于程序内存布局的一些基本概念(RO、RW、ZI和.data、.bss、heap、stack、Flash、SRAM)等相关内容。 希望能为大家提供一些参考或帮助。
文章来自于微信公众号嵌入式电子学习,由火龙果Linda编辑推荐。

内存属性

理解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.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. 初始化栈指针(SP)和程序计数器(PC)。

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

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

  4. 跳转到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文件,找出内存占用大的模块

 

   
103   次浏览       6 次
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程

最新活动计划
企业架构助力业务与IT协作 1-17[在线]
LLM大模型与智能体开发实战 1-17[在线]
AI大模型编写高质量代码 1-14[在线]
AI原生应用的微服务架构 1-22[在线]
需求分析与管理 1-22[北京]
视觉大模型及其应用 1-30[在线]
UAF与企业架构 2-3[北京]
 
 
最新文章
.NET Core 3.0 正式公布:新特性详细解读
.NET Core部署中你不了解的框架依赖与独立部署
C# event线程安全
简析 .NET Core 构成体系
C#技术漫谈之垃圾回收机制(GC)
最新课程
.Net应用开发
C#高级开发技术
.NET 架构设计与调试优化
ASP.NET Core Web 开发
ASP.Net MVC框架原理与应用开发
成功案例
航天科工集团子公司 DotNet企业级应用设计与开发
日照港集 .NET Framewor
神华信 .NET单元测试
台达电子 .NET程序设计与开发
神华信息 .NET单元测试