一文搞懂MCU RAM的分配

一文搞懂MCU RAM的分配

文章目录

  • 一文搞懂MCU RAM的分配
    • 1. 前言
    • 2. 数据段含义
      • 2.1 Program Size解析
    • 3. 局部变量、全局变量、常量如何占用 RAM?
      • 3.1 栈大小Stack_Size 与 堆大小Heap_Size
      • 3.2 验证栈大小设置,对程序编译的影响
      • 3.3 验证局部变量RAM分配
      • 3.4 验证全局变量的RAM分配
      • 3.5 验证全局常量和局部常量const的RAM分配
      • 3.6 关于堆的分配
    • 4. 总结

1. 前言

嵌入式MCU开发,通常受限于硬件资源,往往 RAM 和 ROM 空间有限,大一点的 MCU,RAM 空间也才 512KB,小的更是小的可怜,因此对于 RAM 空间和 ROM 空间是如何被分配走的,想要深入进阶的话是肯定需要了解的。

本文将采用STM32控制器为例,配合实验详细记录/阐述关于嵌入式MCU资源使用那些事!

2. 数据段含义

2.1 Program Size解析

比如我们使用 keil 编译完程序,编译成功之后,会在输出栏提示一行这样的信息

Program Size: Code=4620 RO-data=312 RW-data=16 ZI-data=1168 

这便是我们编译的程序的大小,但这个 Program Size 又如何理解呢?其中的 CodeRO-dataRW-dataZI-data 段分别代表的是什么含义呢?

首先,我们来看看各自段代表的都是什么含义:

  • Code 段:代表的就是我们所编写的代码所占用的 flash 空间。当然需要注意的是,代码段的内容是经过编译器优化以及汇编之后的大小,你在代码里添加注释,或者一些无意义的代码是不会增大code段的大小的。
  • RO-data段:即read only - data,只读数据段。在代码中定义的 const 常量,printf 打印的固定字符等,这些均会被存放到此数据段内。
  • RW-data段:即 read write - data,可读可写段。代码中的变量存放在此数据段内。
  • ZI-data段:即 Zero Initial - data,初始化为0的可读写变量。

明白了每个字段代表的是什么含义,那我们定义的局部变量、全局变量以及常量是如何占用的ram空间的呢?以及具体真正占用 ram 和 rom 空间又是如何计算的呢?

关于占用 ram 和 rom 空间又是如何计算,直接提供给大家以下公式:

  • ROM空间大小计算方式:Total ROM = Code + RO-data + RW-data
  • RAM空间大小计算方式:Total RAM = RW-data + ZI-data

关于局部变量、全局变量、常量如何占用ram,我们接下来采用实验验证推理。

3. 局部变量、全局变量、常量如何占用 RAM?

3.1 栈大小Stack_Size 与 堆大小Heap_Size

在弄清楚我们定义变量如何消耗 RAM 空间之前,我们先要弄清楚栈大小Stack_Size 和堆大小Heap_Size 这两个重要概念!

MCU的RAM空间被划分为多个区域,其中有两个重要组成:堆和栈(当前还有其他区域),此两个区域的大小设置可在对应的启动文件内查看并设置。
在这里插入图片描述
这里我们简单描述下栈和堆是如何使用的:

  • 栈的使用:

    • 在我们程序编码过程中,在函数内部定义一个局部变量,其实是在栈空间上申请了一个内存存放数据;
    • 程序运行过程中,进入某个函数,需要将当前函数内的变量进行现场保存,也即压栈操作,及将当前的寄存器(如r0 r1等)进行压栈操作,也是从栈空间上占用一块内存存放数据;而函数退出的时候,需要进行恢复现场,及出栈操作,也即从栈空间内读取之前进入函数时进行压栈操作的数据,恢复寄存器数据,继续跑后续代码。
  • 堆的使用:

    • 在我们程序执行过程中,调用malloc实际便是从堆上申请一块内存用于使用。使用完成之后,需要调用free释放那块内存区域。
  • 此外针对裸机程序,整个程序中使用的栈,均从启动文件startup_xxxx.s 文件中 Stack_Size 设置的栈空间中分配;而RTOS程序,各线程具有独立的栈空间,这一点需要注意。

接着我们进行如下测试验证。

3.2 验证栈大小设置,对程序编译的影响

  1. 修改栈大小,观察程序编译大小变化,设置 Stack_Size EQU 0x800,程序编译结果:Program Size: Code=3620 RO-data=380 RW-data=20 ZI-data=2124
    在这里插入图片描述
  2. 修改栈大小为Stack_Size EQU 0x400 ,程序编译大小:Program Size: Code=3620 RO-data=380 RW-data=20 ZI-data=1100
    在这里插入图片描述
    比对以上结果可知,栈大小的设置会直接影响程序RAM的消耗,由于上述我们只是简单的修改了栈的大小,缩减了栈的空间,缩减的部分栈并没有被程序使用,故看到的只是ZI-data 数据段的变化。

3.3 验证局部变量RAM分配

  1. 设置启动文件,栈大小为Stack_Size EQU 0x400 ,即分配的栈空间为 0x400 = 1024Byte
  2. 在main()函数内 定义一个局部数组a,数据大小为1Byte,编译程序,查看程序编译大小变化
  3. 注意此处为了防止被编译器优化,我们增加 __attribute__((unused)) 修饰数组
  4. 编译查看程序大小,并运行
    在这里插入图片描述
  5. 程序正常运行
  6. 之后 修改数组a大小为 400Byte,编译查看程序大小,并运行
    在这里插入图片描述
  7. 程序正常运行,编译结果显示 仅code字段发生了变化,RAM空间并未发生改变
  8. 之后再次修改数组a大小超过启动文件内分配的栈大小,设置数组a大小为 1030Byte,编译查看程序大小,并运行
    在这里插入图片描述
  9. 编译结果显示仍然 仅code字段发生了变化,RAM空间并未发生改变,但是实际运行程序会直接崩溃,仿真运行发现程序已卡死在HardFault_Handler()中断
    在这里插入图片描述

综上,可得出以下结论:局部变量会占用栈Stack空间,栈大小Stack_Size在启动文件内设置,当栈空间不足时会导致程序运行崩溃

3.4 验证全局变量的RAM分配

局部变量占用的是系统栈空间,那全局变量是不是也是如此呢?接着我们同样进行验证。

注意,采用全局变量进行验证时,为了让编译器不进行优化,故需要在main函数内增加一行对此数组的使用,我们此处在main函数内增加一行代码如下:printf("%s", a);

  1. 定义数组a为全局变量,大小1Byte,使用__attribute__((unused)) 修饰,防止被编译器优化,编译查看程序编译结果,并运行
    在这里插入图片描述

  2. 编译结果为:Program Size: Code=3600 RO-data=380 RW-data=24 ZI-data=1096 ,程序正常运行

  3. 修改数组a的大小为400Byte,继续验证
    在这里插入图片描述

  4. 编译结果为:Program Size: Code=3600 RO-data=380 RW-data=20 ZI-data=1500 ,此时我们可以发现Total RAM增大了,Add Total Ram = (The current RW-data + The current ZI-data ) - (The laster RW-data + The laster ZI-data) = (1500 + 20) - (1096+ 24) = 400Byte(注意此处400Byte != (400 - 1)Byte 与字节对齐有关) ,程序正常运行

  5. 继续修改数组a大小为1030,使其超过系统栈大小(当前配置的系统栈大小0x400=1024Byte),继续验证
    在这里插入图片描述

  6. 编译结果为:Program Size: Code=3600 RO-data=380 RW-data=20 ZI-data=2132 ,程序正常运行,并没有之前的死机现象,同时我们可以发现Total RAM继续增大,相比上次增大了 Add Total Ram = (The current RW-data + The current ZI-data ) - (The laster RW-data + The laster ZI-data) = (2132 + 20) - (1500 + 20) = 632Byte(注意此处632Byte != (1030 - 400)Byte与字节对齐有关)

综上,通过实验验证可知,定义全局变量不会占用系统栈空间,全局变量不同于局部变量,全局变量从RAM中单独申请空间。

3.5 验证全局常量和局部常量const的RAM分配

如果是const常量会怎样呢?我们在以上两个实验的基础上,增加const修饰变量

  1. 针对全局变量,采用const修饰,使其变为常量,会将此数据分配至RO-data字段,而不再再用RI-data字段
    在这里插入图片描述
  2. 针对局部常量,采用const修饰,与不采用const修饰,程序编译结果均无变化,且由于数组a大小超出系统栈大小,导致程序无法正常运行,故针对局部变量,采用const修饰,仍然占用的是系统栈空间,是不是很震惊!
    在这里插入图片描述
  3. 局部常量使用const会占用系统栈能够理解,修改为const之后,RO-data不会增加吗?这不应该吧。实际上这里又是优化导致(不确定是编译器优化还是c语言优化),由于我们将数组a的值赋值为0,所以被优化了,我们只需要简单的修改一下,让数组a里面不全是0即可。如下图所示,我们可以看到RO-data字段增加了,当然由于局部const常量仍然占用系统栈,且由于系统栈大小不够,故程序仍然无法运行!
    在这里插入图片描述

3.6 关于堆的分配

关于堆的分配及使用比栈就简单多了,如下:

  • 堆大小设置:
    • 堆的总大小,在启动文件内进行设置,Heap_Size EQU 0x200
  • 堆空间的申请:
    • 代码中使用malloc 动态申请,如果有足够大小的堆空间的话,则成功分配,并返回首地址指针;如果堆空闲空间不够,则失败,返回NULL;
  • 堆空间的释放:
    • 堆空间使用完之后,调用 free 动态释放,使用的时候注意不要重复释放,否则容易导致bug!

4. 总结

以上便是针对我们在编程中,编写代码时所会涉及到的RAM空间使用的分析,特别是变量及常量对于ram的占用,总结来说有以下几点:

  • 系统堆栈大小从启动文件进行配置
  • 全局变量不从系统栈分配,直接从ram分配
  • 局部变量从系统栈分配
  • 全局变量分配过大,超过芯片实际ram大小,编译器会帮你直接报错,局部变量分配过大,超过系统栈大小(裸机系统),编译器不会告诉你,程序只会莫名崩溃~
  • 全局常量不占用 RAM,只占用 ROM (RO-data字段)
  • 局部常量不仅占用ROM,还会占用系统栈大小!(裸机系统)

这些都是很细节的东西,想要成为一名资深开发者所必须了解的内容。希望对你有所帮助。

此外需要注意的是,以上是针对裸机系统进行的分析,在RTOS系统中,会有些许差别,主要在于在RTOS中会有系统栈和线程栈,不过也都是栈~

如果你觉得我文章写的不错,欢迎点赞、关注+收藏,谢谢~,以下我的一些推荐:


相关推荐:

  • 专栏:文件系统专栏(点击跳转)

  • 专栏:RT-Thread内核(点击跳转)

  • 专栏:物联网ESP32(点击跳转)

  • 专栏:电机控制专栏(点击跳转)

  • 其他专栏,去主页看看吧~

  • 博文:直流无刷电机FOC控制算法 理论到实践 —— 理论(一)(点击跳转)

  • 博文:直流无刷电机FOC控制算法 理论到实践 —— 理论(二)(点击跳转)

  • 博文:直流无刷电机FOC控制算法 理论到实践 —— 实践(点击跳转)

  • 博客主页:爱出名的狗腿子(点击跳转)


创作不易,转载请注明出处!

关注、点赞+收藏,可快速查收博主有关分享!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/603686.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mac系统安装PicGo时打开报错:文件已损坏

目录 一、前言二、解决方案三、结尾 一、前言 我们在安装某些第三方开发者开发的应用时,无法在 Mac 上运行,提示已经损坏,报以下错误:Mac系统安装PicGo时打开报错:文件已损坏,您应该将它移到废纸篓 二、解决…

LeetCode 2807.在链表中插入最大公约数

【LetMeFly】2807.在链表中插入最大公约数 力扣题目链接:https://leetcode.cn/problems/insert-greatest-common-divisors-in-linked-list/ 给你一个链表的头 head ,每个结点包含一个整数值。 在相邻结点之间,请你插入一个新的结点&#x…

DQL命令查询数据(三)

本课目标 掌握MySQL的多表查询 SQL语句的综合应用 多表连接查询 通过各个表之间共同列的关联性(例如:外键)来查询的 分类: 内连接(INNER JOIN) ,可简写为 JOIN;左外连接(LEFT OUTER JOIN),…

Java技术栈 —— Hadoop入门(一)

Java技术栈 —— Hadoop入门(一) 一、Hadoop第一印象二、安装Hadoop三、Hadoop解析3.1 Hadoop生态介绍3.1.1 MapReduce - 核心组件3.1.2 HDFS - 核心组件3.1.3 YARN - 核心组件3.1.4 其它组件3.1.4.1 HBase3.1.4.2 Hive3.1.4.3 Spark 一、Hadoop第一印象…

【Project】TPC-Online Module (manuscript_2024-01-07)

PRD正文 一、概述 本模块实现隧道点云数据的线上汇总和可视化。用户可以通过注册和登录功能进行身份验证,然后上传原始隧道点云数据和经过处理的数据到后台服务器。该模块提供数据查询、筛选和可视化等操作,同时支持对指定里程的分段显示和点云颜色更改…

小游戏实战丨基于PyGame的消消乐小游戏

文章目录 写在前面PyGame消消乐注意事项系列文章写在后面 写在前面 本期内容:基于pygame实现喜羊羊与灰太狼版消消乐小游戏 下载地址:https://download.csdn.net/download/m0_68111267/88700193 实验环境 python3.11及以上pycharmpygame 安装pygame…

回溯算法part01 算法

回溯算法part01 今日内容: ● 理论基础 ● 77. 组合 1.LeetCode77. 组合 https://leetcode.cn/problems/combinations/ 模板 //回溯算法模板void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节…

西电期末1027.判断同构数

一.题目 二.分析与思路 不用把他转成字符串再转成数字之类的&#xff0c;用数学解决就好&#xff01;找出一个数的最后位就是将其对求余啊&#xff0c;找一个数有几位以前也有过啊&#xff0c;那不就过了嘛&#xff01; 三.代码实现 #include<bits/stdc.h>//万能头 in…

模板元编程简介

从引入 template 关键字开始&#xff0c;C里就出现了泛型编程&#xff0c;而又泛型编程衍生出的模板元编程&#xff08;template meta_programming&#xff0c;简称“元编程”&#xff09;则是众多编程范式中最复杂、最强大和最具有权威的一种。所谓“元编程”——metaprogramm…

Keil C51的编译器限制

编译器限制 Cx51 编译器体现了下面列出的一些已知限制。在大多数情况下&#xff0c;对 C 语言的组件没有限制;例如&#xff0c;您可以在 switch 块中指定无限数量的符号或 case 语句。如果有足够的地址空间&#xff0c;则可以定义数千个符号。 最多支持对任何标准数据类型的 …

二叉树的经典算法(算法村第八关青铜挑战)

二叉树里的双指针 所谓的双指针就是定义了两个变量&#xff0c;在二叉树中有需要至少定义两个变量才能解决问题。这两个指针可能针对一棵树&#xff0c;也可能针对两棵树&#xff0c;姑且也称之为“双指针”。这些问题一般与对称、反转和合并等类型题相关。 判断两棵树是否相…

Python:tqdm模块详解

tqdm 是一个用于在 Python 中显示进度条的模块&#xff0c;用于在循环或迭代过程中展示任务的进度。 1. 安装 首先&#xff0c;你可以通过 pip 安装 tqdm 模块&#xff1a; pip install tqdm2. 基本使用方法 在 Python 中使用 tqdm &#xff0c;只需将你的迭代对象传递给 tqd…

【Linux】之搭建 PostgreSQL 环境

前言 在 Linux 系统下安装 PostgreSQL&#xff0c;可以选择快捷方便的 Docker 安装&#xff0c;但正常的服务器都是直接原生安装的&#xff0c;所以&#xff0c;这里我将讲解如何正常安装 PostgreSQL 以及安装之后的一些配置。如果想了解 Docker 安装的话&#xff0c;可以查看我…

竞赛练一练 第27期:GESP和电子学会相关题目练习

GESP一级2023.03_小猫捉老鼠 1. 准备工作 (1)导入背景Room 2; (2)删除默认小猫角色,导入角色Mouse1、Cat 2。 2. 功能实现 (1)点击绿旗,老鼠出现在随机位置; (2)通过键盘的“↑”、“↓”、“←”、“→”键来控制小猫行走,每按一次,移动5步; (3)小猫在…

使用openCV进行图像处理

使用 openCV进行图像处理 使用 openCV进行图像处理&#xff0c;又名&#xff1a;学习计算机视觉理论&#xff0c;做 demo(第3 天&#xff09; 目录 2.1 图像模糊 2.1.1 均值滤波2.1.2 中值滤波2.1.3 高斯滤波2.1.4 案例实现 2.2 图像锐化 2.2.1 图像锐化简介2.2.2 案例实现 …

Spring依赖注入的魔法:深入DI的实现原理【beans 五】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Spring依赖注入的魔法&#xff1a;深入DI的实现原理【beans 五】 前言DI的基本概念基本概念&#xff1a;为什么使用依赖注入&#xff1a; 构造器注入构造器注入的基本概念&#xff1a;示例&#xff1a…

laravel-admin之 浏览器自动填充密码(如果需要渲染数据库密码的话,首先确认数据库密码是否可以逆向解密)

参考 https://blog.51cto.com/u_10401840/5180106 为什么浏览器端保存的密码一直自动写入到$form->password 解决办法 2、在页面进入的时候&#xff0c;默认表单的type值为text&#xff1b;推荐指数&#xff1a;2颗星 5、设置表单的readonly属性;推荐指数&#xff1a;4颗…

实习遇到问题备忘录

1.Hutool工具包的DB Hutool学习 —— 数据库 - db &#xff08;一&#xff09;Db简单操作 - 简书 (jianshu.com) 2.Consumer函数接口 Java 常用函数式接口之Consumer接口 - LeeHua - 博客园 (cnblogs.com) 3.sql高级用法merge into SQL高级知识——MERGE INTO - 知乎 (zhi…

Linux 上 Nginx 配置访问 web 服务器及配置 https 访问配置过程记录

目录 一、前言说明二、配置思路三、开始修改配置四、结尾 一、前言说明 最近自己搭建了个 Blog 网站&#xff0c;想把网站部署到服务器上面&#xff0c;本文记录一下搭建过程中 Nginx 配置请求转发的过程。 二、配置思路 web项目已经在服务器上面运行起来了&#xff0c;运行的端…

222.【2023年华为OD机试真题(C卷)】分配土地(扫描线算法-JavaPythonC++JS实现)

🚀点击这里可直接跳转到本专栏,可查阅顶置最新的华为OD机试宝典~ 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目-分配土地二.解题思路三.题解代码Python题解代码…