单片机内存管理剖析
一、概述
在单片机系统中,内存资源通常是有限的,因此高效的内存管理至关重要。合理地分配和使用内存可以提高系统的性能和稳定性,避免内存泄漏和碎片化问题。单片机的内存主要包括程序存储器(如 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.…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !
我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...
jdbc查询mysql数据库时,出现id顺序错误的情况
我在repository中的查询语句如下所示,即传入一个List<intager>的数据,返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致,会导致返回的id是从小到大排列的,但我不希望这样。 Query("SELECT NEW com…...
