单片机内存管理剖析
一、概述
在单片机系统中,内存资源通常是有限的,因此高效的内存管理至关重要。合理地分配和使用内存可以提高系统的性能和稳定性,避免内存泄漏和碎片化问题。单片机的内存主要包括程序存储器(如 Flash)和数据存储器(如 RAM),其中数据存储器又可进一步分为静态数据区、栈区和堆区。动态内存分配主要发生在堆区,而 sbrk
、malloc
和 free
这三个函数 在堆内存管理中起着关键作用。
二、sbrk
:底层的内存边界调整
2.1 原理
sbrk
是一个底层的系统调用(在某些单片机库中也有对应的实现),其核心功能是调整进程数据段的结束地址,也就是 break
指针。通过改变 break
指针的位置,可以实现堆内存的扩展和收缩。当传入一个正的增量值时,break
指针向后移动,堆内存得到扩展;当传入一个负的增量值时,break
指针向前移动,堆内存被收缩。
2.2 源码示例与解释
#include <stdint.h>
#include <errno.h>// 假设这是链接脚本定义的堆起始和结束地址
extern char _end[];
extern char _heap_end[];
// 当前堆指针
static char *curbrk = _end;
// sbrk 函数实现
void *_sbrk(int incr) {char *old_brk = curbrk;char *new_brk = curbrk + incr;// 边界检查if (new_brk < _end || new_brk > _heap_end) {errno = ENOMEM; // 设置错误号表示内存不足return (void *)-1;}curbrk = new_brk;return (void *)old_brk;
}
- 全局变量
_end
:由链接脚本确定,代表堆的起始地址。_heap_end
:同样由链接脚本确定,代表堆的最大可用地址。curbrk
:静态变量,记录当前堆的结束地址,初始化为_end
。
- 函数逻辑
- 保存当前的
curbrk
到old_brk
中,这将作为函数的返回值。 - 根据传入的
incr
计算新的堆结束地址new_brk
。 - 进行边界检查,确保
new_brk
在合法范围内(不小于_end
且不大于_heap_end
)。如果超出范围,设置errno
为ENOMEM
并返回(void *)-1
表示内存分配失败。 - 如果边界检查通过,更新
curbrk
为new_brk
,并返回old_brk
,它指向新分配内存的起始位置。
- 保存当前的
2.3 使用场景和注意事项
- 使用场景:
sbrk
通常作为底层的内存分配原语,为更高级的内存分配函数(如malloc
)提供支持。在一些简单的单片机应用中,如果只需要简单的内存扩展和收缩操作,也可以直接使用sbrk
。 - 注意事项
- 由于
sbrk
直接操作堆的边界,使用不当可能会导致内存越界访问,破坏其他重要的数据。 sbrk
分配的内存是连续的,频繁的扩展和收缩操作可能会导致内存碎片化,降低内存的利用率。
- 由于
三、malloc
:用户级的动态内存分配
3.1 原理
malloc
是 C 标准库中提供的用于动态内存分配的函数,它建立在 sbrk
的基础之上。malloc
函数的主要任务是根据用户请求的内存大小,在堆中找到合适的空闲内存块并返回其起始地址。为了管理堆中的空闲内存,malloc
通常会维护一个空闲块链表,使用不同的分配策略(如首次适配、最佳适配等)来查找合适的空闲块。
3.2 源码示例与解释
#include <stdio.h>
#include <stdint.h>
#include <errno.h>
// 内存块结构体
typedef struct mem_block {size_t size;int is_free;struct mem_block *next;
} MemBlock;// 空闲链表头指针
static MemBlock *free_list = NULL;// 分配内存
void *malloc(size_t size) {MemBlock *current = free_list;MemBlock *prev = NULL;// 查找合适的空闲块while (current != NULL) {if (current->is_free && current->size >= size) {current->is_free = 0;// 如果空闲块比需求大,分割空闲块if (current->size > size + sizeof(MemBlock)) {MemBlock *new_free_block = (MemBlock *)((char *)current + sizeof(MemBlock) + size);new_free_block->size = current->size - size - sizeof(MemBlock);new_free_block->is_free = 1;new_free_block->next = current->next;current->size = size;current->next = new_free_block;}return (void *)(current + 1);}prev = current;current = current->next;}// 没有合适的空闲块,调用 sbrk 扩展堆size_t total_size = size + sizeof(MemBlock);MemBlock *new_block = (MemBlock *)_sbrk(total_size);if (new_block == (MemBlock *)-1) {return NULL;}new_block->size = size;new_block->is_free = 0;new_block->next = NULL;if (prev != NULL) {prev->next = new_block;} else {free_list = new_block;}return (void *)(new_block + 1);
}
-
数据结构
-
MemBlock
结构体:用于表示堆中的内存块,包含三个成员:
size
:记录内存块的大小。is_free
:标记该内存块是否空闲。next
:指向下一个内存块的指针,用于构建空闲块链表。
-
free_list
:指向空闲块链表的头指针,初始化为NULL
。
-
-
函数逻辑
- 查找空闲块:遍历空闲块链表
free_list
,使用首次适配策略查找第一个大小足够的空闲块。 - 分割空闲块:如果找到的空闲块比请求的大小大,将其分割为两部分:一部分用于满足当前请求,另一部分作为新的空闲块插入到链表中。
- 扩展堆:如果在空闲块链表中没有找到合适的空闲块,调用
sbrk
函数扩展堆空间,分配一块新的内存,并将其初始化为一个新的内存块。 - 返回内存地址:返回分配的内存块的起始地址(跳过
MemBlock
结构体部分)。
- 查找空闲块:遍历空闲块链表
四、free
:动态内存的释放
4.1 原理
free
函数用于释放 malloc
、calloc
或 realloc
分配的内存块。当调用 free
时,它会将指定的内存块标记为空闲,并尝试合并相邻的空闲块,以减少内存碎片化。
4.2 源码示例与解释
// 释放内存
void free(void *ptr) {if (ptr == NULL) return;// 获取内存块头部MemBlock *block = (MemBlock *)ptr - 1;block->is_free = 1;// 合并相邻的空闲块MemBlock *current = free_list;MemBlock *prev = NULL;// 找到合适的插入位置while (current != NULL && current < block) {prev = current;current = current->next;}// 合并前一个空闲块if (prev != NULL && prev->is_free) {prev->size += block->size + sizeof(MemBlock);prev->next = block->next;block = prev;}// 合并后一个空闲块if (current != NULL && current->is_free) {block->size += current->size + sizeof(MemBlock);block->next = current->next;}// 如果没有前一个块,更新空闲链表头if (prev == NULL) {free_list = block;} else {prev->next = block;}block->next = current;
}
- 函数逻辑
- 空指针检查:如果传入的指针
ptr
为NULL
,直接返回,不进行任何操作。 - 标记为空闲:通过指针计算得到内存块的头部信息(
MemBlock
结构体),将其is_free
标记设置为 1,表示该内存块已空闲。 - 合并相邻空闲块
- 遍历空闲块链表,找到合适的位置插入该空闲块。
- 检查前一个和后一个内存块是否空闲,如果是,则将它们合并成一个更大的空闲块。
- 更新空闲链表:根据合并结果更新空闲块链表的指针,确保链表的正确性。
- 空指针检查:如果传入的指针
4.3 使用场景和注意事项
- 使用场景:在不再需要使用动态分配的内存时,必须调用
free
函数释放内存,以避免内存泄漏。 - 注意事项
- 只能释放由
malloc
、calloc
或realloc
分配的内存,释放其他内存可能会导致未定义行为。 - 不要多次释放同一块内存,这会导致双重释放错误,可能会破坏内存管理数据结构。
- 只能释放由
sbrk
、malloc
和 free
是单片机内存管理中重要的工具,它们相互协作,实现了堆内存的动态分配和释放。sbrk
作为底层的系统调用,提供了基本的内存扩展和收缩功能;malloc
基于 sbrk
实现了用户级的动态内存分配接口,方便程序员在运行时分配所需的内存;free
则负责释放不再使用的内存,避免内存泄漏和碎片化。在实际应用中,需要合理使用这些函数,注意内存的分配和释放规则,以确保系统的稳定性和性能。
相关文章:

单片机内存管理剖析
一、概述 在单片机系统中,内存资源通常是有限的,因此高效的内存管理至关重要。合理地分配和使用内存可以提高系统的性能和稳定性,避免内存泄漏和碎片化问题。单片机的内存主要包括程序存储器(如 Flash)和数据存储器&a…...

【gopher的java学习笔记】Java中Service与Mapper的关系详解
在后端开发中,Java作为一种广泛使用的编程语言,其架构设计和层次划分对于系统的可维护性、可扩展性和性能有着至关重要的影响。特别是在使用MyBatis等持久层框架时,Service层与Mapper层的关系更是值得深入探讨。本文将从Java Web应用程序的角…...

2025美赛B题完整代码+建模过程
问题一 为朱诺市建立一个可持续旅游产业模型。具体要求包括考虑游客数量、总收入,以及为稳定旅游业而实施的措施,明确优化因素和约束条件,并制定额外收入的支出计划,展示这些支出如何反馈到模型中以促进可持续旅游业发展,同时进行敏感性分析,讨论哪些因素最为重要。 为了…...

【MySQL】我在广州学Mysql 系列——MySQL用户管理详解
ℹ️大家好,我是练小杰,本博客是春节前最后一篇了,在此感谢大佬们今年的支持!!🙏🙏 接下来将学习MYSQL用户管理的相关概念以及命令~~ 回顾:👉【MYSQL触发器的使用】 数据…...

Linux-rt下卡死之hrtimer分析
Linux-rt下卡死之hrtimer分析 日志 超时读过程分析 #define readl_poll_timeout(addr, val, cond, delay_us, timeout_us) \readx_poll_timeout(readl, addr, val, cond, delay_us, timeout_us)34 #define readx_poll_timeout(op, addr, val, cond, sleep_us, timeout_us) \…...

【AI日记】25.01.24
【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】 AI kaggle 比赛:Forecasting Sticker Sales 读书 书名:法治的细节作者:罗翔 律己 AI:8 小时,良作息:00:30-8:30&…...

React 中hooks之useSyncExternalStore使用总结
1. 基本概念 useSyncExternalStore 是 React 18 引入的一个 Hook,用于订阅外部数据源,确保在并发渲染下数据的一致性。它主要用于: 订阅浏览器 API(如 window.width)订阅第三方状态管理库订阅任何外部数据源 1.1 基…...

C++11新特性之decltype
1.decltype的作用 decltype是C11新增的一个关键字,与auto的功能一样,都是在编译期间推导变量类型的。不了解auto的可以转到——C11新特性之auto。 为什么引入decltype?看过上边那篇博客的读者应该知道auto在有些场景中并不适用,所以引入declt…...

二叉树相关oj题 1. 检查两颗树是否相同。
二叉树相关oj题 检查两颗树是否相同。OJ链接 另一颗树的子树。OJ链接 if(rootnull)易漏掉 会导致空指针异常翻转二叉树。OJ链接...

element tbas增加下拉框
使用Tabs 标签页的label插槽,嵌入Dropdown 下拉菜单,实现Tabs 标签页增加下拉切换功能 Tabs 标签页 tab-click"事件"(这个事件当中到拥有下拉框的tab里时,可以存一下Dropdown 第一个菜单的id,实现点击到拥有…...

新浪安卓(Android)开发面试题及参考答案(68道题,9道手撕题)
链表判环,找入口 思路: 判断是否有环:使用快慢指针,快指针每次走两步,慢指针每次走一步,如果它们相遇,说明有环。找出环入口:当判断出有环后,将慢指针重新指向头节点,然后快慢指针同时以相同速度移动,再次相遇的节点就是环的入口。以下是判断链表是否有环以及找出环…...

Zbrush导入笔刷
Zbrush笔刷目录: ...\Zbrush\ZStartup\BrushPresets...

实战演示:利用ChatGPT高效撰写论文
在当今学术界,撰写论文是一项必不可少的技能。然而,许多研究人员和学生在写作过程中常常感到困惑和压力。幸运的是,人工智能的快速发展为我们提供了新的工具,其中ChatGPT便是一个优秀的选择。本文将通过易创AI创作平台,…...

大数据学习之SCALA分布式语言三
7.集合类 111.可变set一 112.可变set二 113.不可变MAP集合一 114.不可变MAP集合二 115.不可变MAP集合三 116.可变map一 package com . itbaizhan . chapter07 //TODO 2. 使用 mutable.Map 前导入如下包 import scala . collection . mutable // 可变 Map 集合 object Ma…...

k8s简介,k8s环境搭建
目录 K8s简介环境搭建和准备工作修改主机名(所有节点)配置静态IP(所有节点)关闭防火墙和seLinux,清除iptables规则(所有节点)关闭交换分区(所有节点)修改/etc/hosts文件&…...

深入理解MySQL事务(万字详)
文章目录 什么是事务为什么会出现事务事务的版本支持事务的提交方式事务常见操作方式正常演示 - 证明事务的开始与回滚非正常演示1 - 证明未commit,客户端崩溃,MySQL自动会回滚(隔离级别设置为读未提交)非正常演示2 - 证明commit了…...

微信小程序使用picker根据接口给的省市区的数据实现省市区三级联动或者省市区街道等多级联动
接口数据如上图 省市区多级联动,都是使用的一个接口通过传参父类的code。返回我们想要的数据 比如获取省就直接不要参数。市就把省得code传给接口,区就把市的code作为参数。 <picker mode"multiSelector" :range"mulSelect1" …...

Go Fx 框架使用指南:深入理解 Provide 和 Invoke 的区别
1. 什么是 Fx 框架? Fx 是一个基于 Go 语言的依赖注入框架,专注于简化应用程序的生命周期管理和依赖的构建。在复杂的应用程序中,Fx 通过模块化的设计方式将组件连接起来,使开发者能够更高效地管理依赖关系。 Fx 的核心理念是&a…...

VSCode+Continue实现AI辅助编程
Continue是一款功能强大的AI辅助编程插件,可连接多种大模型,支持代码设计优化、错误修正、自动补全、注释编写等功能,助力开发人员提高工作效率与代码质量。以下是其安装和使用方法: 一、安装VSCode 参见: vscode安…...

阿里云服务器在Ubuntu上安装redis并使用
1、redis安装 sudo apt install lsb-release curl gpgcurl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpgecho "deb [signed-by/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.…...

Blazor-Blazor呈现概念
静态和交互式呈现概念 在Blazor开发中,Razor 组件具备两种重要的呈现方式,分别是静态呈现和交互式呈现。 静态呈现 也被称为静态渲染,是一种典型的服务器端方案。在这种模式下,组件呈现时,用户与.NET/C# 代码之间缺…...

14-6-2C++的list
(一)list对象的带参数构造 1.list(elem);//构造函数将n个elem拷贝给本身 #include <iostream> #include <list> using namespace std; int main() { list<int> lst(3,7); list<int>::iterator it; for(itlst.begi…...

StarRocks常用命令
目录 1、StarRocks 集群管理&配置命令 2、StarRocks 常用操作命令 3、StarRocks 数据导入和导出 1、StarRocks 集群管理&配置命令 查询 FE 节点信息 SHOW frontends; SHOW PROC /frontends; mysql -h192.168.1.250 -P9030 -uroot -p -e "SHOW PROC /dbs;"…...

激光雷达和相机早期融合
通过外参和内参的标定将激光雷达的点云投影到图像上。 • 传感器标定 首先需要对激光雷达和相机(用于获取 2D 图像)进行外参和内参标定。这是为了确定激光雷达坐标系和相机坐标系之间的转换关系,包括旋转和平移。通常采用棋盘格等标定工具&…...

PMP–一、二、三模–分类–12.采购管理
文章目录 技巧十二、采购管理 一模12.采购管理--3.控制采购--输出--风险登记册--每个被选中的卖方都会带来特殊的风险。随着早期风险的过时以及新风险的出现,在项目执行期间对风险登记册进行变更。 供应商还未开始做,是一个风险,当做风险进行…...

C++ 标准模板库 (STL, Standard Template Library)
声明:大佬们~这是Tubishu在追寻stl过程中偶然得到了“颢天”大佬的笔记,shushu感觉非常有帮助🔥又颢天佬未曾来过CSDN,索性在此传达颢天大佬的功德🧎 传送门在此➡️颢天笔记✨✨ C 标准模板库 (STL, Standard Templa…...

从spec到iso的koji使用
了解一下Linux发行版流程::从spec到iso的koji使用 for Fedora 41。 Fedora 41有24235个包,我们选择 minimal 的几十个源码包,百多个rpm包构建。 配3台服务器 40C64G 48C64G 80C128G,有点大材小用,一台就够了 …...

【记录自开发的SQL工具】工具字符拼接、Excel转sql、生成编码、生成测试数据
记录自己开发的一个SQL聚合工具 功能介绍: 文本加引号 给多行文本前后添加引号,并用逗号连接,直接复制到 sql 中的 in 条件中 Excel转SQL 适用于将Excel表格的数据,批量导入到数据库的场景 此工具能快速将excel表格转换为i…...

Cesium特效——城市白模的科技动效的各种效果
最终效果图如下: 实现方法: 步骤一:使用cesiumlib生产白模,格式为3dtiles 注意事项:采用其他方式可能导致白模贴地,从而导致不能实现该效果,例如把步骤二的服务地址改为Cesium Sandcastle 里的…...

VS Code i18n国际化组件代码code显示中文配置 i18n ally
VUE项目做i18n国际化之后,代码中的中文都变成了code这时的代码就会显得非常难读,如果有一个插件能把code转换成中文显示就好了 vscode插件搜索“i18n ally” 在项目根文件夹下创建文件:.vscode/settings.json settings.json 内容如下 {"…...