单片机内存管理剖析

news2025/1/27 8:45:17

一、概述

在单片机系统中,内存资源通常是有限的,因此高效的内存管理至关重要。合理地分配和使用内存可以提高系统的性能和稳定性,避免内存泄漏和碎片化问题。单片机的内存主要包括程序存储器(如 Flash)和数据存储器(如 RAM),其中数据存储器又可进一步分为静态数据区、栈区和堆区。动态内存分配主要发生在堆区,而 sbrkmallocfree 这三个函数 在堆内存管理中起着关键作用。

二、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
  • 函数逻辑
    1. 保存当前的 curbrkold_brk 中,这将作为函数的返回值。
    2. 根据传入的 incr 计算新的堆结束地址 new_brk
    3. 进行边界检查,确保 new_brk 在合法范围内(不小于 _end 且不大于 _heap_end)。如果超出范围,设置 errnoENOMEM 并返回 (void *)-1 表示内存分配失败。
    4. 如果边界检查通过,更新 curbrknew_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

  • 函数逻辑

    1. 查找空闲块:遍历空闲块链表 free_list,使用首次适配策略查找第一个大小足够的空闲块。
    2. 分割空闲块:如果找到的空闲块比请求的大小大,将其分割为两部分:一部分用于满足当前请求,另一部分作为新的空闲块插入到链表中。
    3. 扩展堆:如果在空闲块链表中没有找到合适的空闲块,调用 sbrk 函数扩展堆空间,分配一块新的内存,并将其初始化为一个新的内存块。
    4. 返回内存地址:返回分配的内存块的起始地址(跳过 MemBlock 结构体部分)。

在这里插入图片描述

四、free:动态内存的释放

4.1 原理

free 函数用于释放 malloccallocrealloc 分配的内存块。当调用 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;
}
  • 函数逻辑
    1. 空指针检查:如果传入的指针 ptrNULL,直接返回,不进行任何操作。
    2. 标记为空闲:通过指针计算得到内存块的头部信息(MemBlock 结构体),将其 is_free 标记设置为 1,表示该内存块已空闲。
    3. 合并相邻空闲块
      • 遍历空闲块链表,找到合适的位置插入该空闲块。
      • 检查前一个和后一个内存块是否空闲,如果是,则将它们合并成一个更大的空闲块。
    4. 更新空闲链表:根据合并结果更新空闲块链表的指针,确保链表的正确性。

4.3 使用场景和注意事项

  • 使用场景:在不再需要使用动态分配的内存时,必须调用 free 函数释放内存,以避免内存泄漏。
  • 注意事项
    • 只能释放由 malloccallocrealloc 分配的内存,释放其他内存可能会导致未定义行为。
    • 不要多次释放同一块内存,这会导致双重释放错误,可能会破坏内存管理数据结构。

在这里插入图片描述

sbrkmallocfree 是单片机内存管理中重要的工具,它们相互协作,实现了堆内存的动态分配和释放。sbrk 作为底层的系统调用,提供了基本的内存扩展和收缩功能;malloc 基于 sbrk 实现了用户级的动态内存分配接口,方便程序员在运行时分配所需的内存;free 则负责释放不再使用的内存,避免内存泄漏和碎片化。在实际应用中,需要合理使用这些函数,注意内存的分配和释放规则,以确保系统的稳定性和性能。

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

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

相关文章

Qt简单迷宫游戏

目录 你将学到你将准备你将改变你将设计你将编程开始界面游玩界面胜利界面其它bug修复 你可扩展下一篇博客要说的东西 你将学到 Qt中QKeySequence对象的基本创建Qt中QShortcut对象的基本应用Qt中QSoundEffect对象的基本应用 你将准备 在开始制作Qt简单迷宫游戏之前&#xff…

Ansys Thermal Desktop 概述

介绍 Thermal Desktop 是一种用于热分析和流体分析的通用工具。它可用于组件或系统级分析。 来源&#xff1a;CRTech 历史 Thermal Desktop 由 C&R Technologies (CR Tech) 开发。它采用了 SINDA/FLUINT 求解器。SINDA/FLUINT 最初由 CR Tech 的创始人为 NASA 的约翰逊航…

WPF基础 | WPF 基础概念全解析:布局、控件与事件

WPF基础 | WPF 基础概念全解析&#xff1a;布局、控件与事件 一、前言二、WPF 布局系统2.1 布局的重要性与基本原理2.2 常见布局面板2.3 布局的测量与排列过程 三、WPF 控件3.1 控件概述与分类3.2 常见控件的属性、方法与事件3.3 自定义控件 四、WPF 事件4.1 路由事件概述4.2 事…

西门子【Library of General Functions (LGF) for SIMATIC S7-1200 / S7-1500】

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 通用函数库 (LGF) 扩展了 TIA Portal 中用于 PLC 编程的 STEP 7 指令&#xff08;数学函数、时间、计数器 等&#xff09;。该库可以不受限制地使用&#xff0c;并包含 FIFO 、搜索功能、矩阵计算、 astro 计…

Android实战经验篇-AndroidScrcpyClient投屏一

系列文章转如下链接&#xff1a; Android Display Graphics系列文章-汇总 Android实战经验篇-系列文章汇总 本文主要包括部分&#xff1a; 一、方案说明 1.1 适用场景 1.2 方案框架 二、功能演示 2.1 环境准备 2.2 演示 一、方案说明 1.1 适用场景 优秀的开源的scrc…

从 Spark 到 StarRocks:实现58同城湖仓一体架构的高效转型

作者&#xff1a;王世发&#xff0c;吴艳兴等&#xff0c;58同城数据架构部 导读&#xff1a; 本文介绍了58同城在其数据探查平台中引入StarRocks的实践&#xff0c;旨在提升实时查询性能。在面对传统Spark和Hive架构的性能瓶颈时&#xff0c;58同城选择StarRocks作为加速引擎&…

wangEditor富文本编辑器,Laravel上传图片配置和使用

文章目录 前言步骤1. 构造好前端模版2. 搭建后端存储3. 调试 前言 由于最近写项目需要使用富文本编辑器&#xff0c;使用的是VUE3.0版本所以很多不兼容&#xff0c;实际测试以后推荐使用wangEditor 步骤 构造好前端模版搭建后端存储调试 1. 构造好前端模版 安装模版 模版安…

【MySQL】我在广州学Mysql 系列——MySQL用户管理详解

ℹ️大家好&#xff0c;我是练小杰&#xff0c;本博客是春节前最后一篇了&#xff0c;在此感谢大佬们今年的支持&#xff01;&#xff01;&#x1f64f;&#x1f64f; 接下来将学习MYSQL用户管理的相关概念以及命令~~ 回顾&#xff1a;&#x1f449;【MYSQL触发器的使用】 数据…

2025年数学建模美赛 A题分析(3)楼梯使用方向偏好模型

2025年数学建模美赛 A题分析&#xff08;1&#xff09;Testing Time: The Constant Wear On Stairs 2025年数学建模美赛 A题分析&#xff08;2&#xff09;楼梯磨损分析模型 2025年数学建模美赛 A题分析&#xff08;3&#xff09;楼梯使用方向偏好模型 2025年数学建模美赛 A题分…

Spring Security(maven项目) 3.0.2.7版本

通过实践而发现真理&#xff0c;又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识&#xff0c;又从理性认识而能动地指导革命实践&#xff0c;改造主观世界和客观世界。实践、认识、再实践、再认识&#xff0c;这种形式&#xff0c;循环往复以至无穷&#xf…

【二叉树】4. 判断一颗二叉树是否是平衡二叉树。5. 对称二叉树。6. 二叉树的构建及遍历 7. 二叉树的分层遍历 。

判断一颗二叉树是否是平衡二叉树。OJ链接 可以在求树高度的过程中判断树是否平衡 对称二叉树。OJ链接 二叉树的构建及遍历。OJ链接 注意&#xff1a;public static int i最好把static去掉 否则当有多个测试用例时 i无法重新为0二叉树的分层遍历 。OJ链接 但此题要求返回List…

Java如何实现反转义

Java如何实现反转义 前提 最近做的一个需求&#xff0c;是热搜词增加换一批的功能。功能做完自测后&#xff0c;交给了测试伙伴&#xff0c;但是测试第二天后就提了一个bug&#xff0c;出现了未知词 levis。第一眼看着像公司售卖的一个品牌-李维斯。然后再扒前人写的代码&…

“大模型横扫千军”背后的大数据挖掘--浅谈MapReduce

文章目录 O 背景知识1 数据挖掘2 邦费罗尼原则3 TF.IDF4 哈希函数5 分布式文件系统 一、MapReduce基本介绍1. Map 任务2. 按键分组3. Reduce 任务4. 节点失效处理5.小测验&#xff1a;在一个大型语料库上有100个map任务和若干reduce任务&#xff1a; 二、基于MapReduce的基本运…

蓝桥杯3519 填充 | 分类讨论

题目传送门 很简单&#xff0c;遍历一次字符串&#xff0c;将‘?’作为0或1处理&#xff0c;发现00和11统计次数即可。 s str(input()) cnt 0 arr [00, 11, 0?, ?0, 1?, ?1, ??] i0 while i < len(s)-1:if s[i:(i2)] in arr:i 2cnt 1else:i 1 print(cnt)END✨

嵌入式知识点总结 ARM体系与架构 专题提升(一)-硬件基础

嵌入式知识点总结 ARM体系与架构 专题提升(一)-硬件基础 目录 1.NAND FLASH 和NOR FLASH异同 ? 2.CPU,MPU,MCU,SOC,SOPC联系与差别? 3.什么是交叉编译&#xff1f; 4.为什么要交叉编译&#xff1f; 5.描述一下嵌入式基于ROM的运行方式和基于RAM的运行方式有什么区别? 1…

EchoMimicV2的部署使用

最近有一个录课的需要&#xff0c;我不想浪费人力&#xff0c;只想用技术解决。需求很简单&#xff0c;就是用别人现成的录课视频中的形象和声线&#xff0c;再结合我提供的讲稿去生成一个新的录课视频。我觉得应该有现成的技术了&#xff0c;我想要免费大批量生产。最近看到这…

迅为RK3568开发板篇OpenHarmony实操HDF驱动控制LED-添加内核编译

编译内核时将该 HDF 驱动编译到镜像中&#xff0c;接下来编写驱动编译脚本 Makefile&#xff0c;代码如下所示&#xff1a; 加入编译体系&#xff0c;填加模块目录到 drivers/hdf_core/adapter/khdf/linux/Makefile 文件 更多内容可以关注&#xff1a;迅为RK3568开发板篇OpenHa…

期权帮|在股指期货中超过持仓限额怎么办?

锦鲤三三每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 在股指期货中超过持仓限额怎么办&#xff1f; 一、立即平仓或减仓&#xff1a; &#xff08;1&#xff09;最直接且有效的方法是立即平仓或减仓&#xff0c;以降低持仓量至限额…

Linux 高级路由与流量控制-用 tc qdisc 管理 Linux 网络带宽

大家读完记得觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 此分享内容比较专业&#xff0c;很多与硬件和通讯规则及队列&#xff0c;比较底层需要有技术功底人员深入解读。 Linux 的带宽管理能力 足以媲美许多高端、专用的带宽管理系统。 1 队列&#xff0…

openstack单机安装

openstack单机安装 网卡配置安装依赖开启虚拟环境修改配置文件 部署openstack部署openstack客户端访问可视化界面Horizon补充 本篇主要讲述Ubuntu2204单机安装openstackstable/2024.2。其他版本的Linux系统或者openstack版本&#xff0c;请参考openstack官网。 网卡配置 需要配…