news 2026/8/28 20:30:28

嵌入式现代C++教程——自定义分配器(Allocator)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式现代C++教程——自定义分配器(Allocator)

嵌入式现代C++教程——自定义分配器(Allocator)

在嵌入式世界里,内存不是“无限”的抽屉,而是那只随时会嫌你占空间的行李箱。默认的new/malloc对我们友好吗?有时候很友好(没错,方便);但更多时候,它们是潜在的性能炸弹、不可预测的延迟来源、以及碎片化萌生地。于是,写一个“自定义分配器”——你自己的内存管理策略,就变成了工程师的基本修行。


为什么要自定义分配器?

想象一下这些场景:实时任务不能被偶发的malloc阻塞;启动阶段需要一次性分配若干对象以避免运行时分配;小对象分配频繁但大小恒定;或者你想把一大块内存划分给特定模块,便于追踪与回收。默认分配器往往无法同时满足:确定性、低内存占用、低碎片和高性能。

自定义分配器修改了像内存申请的具体模式,我们可以接入自己的固定大小池、栈式分配、快速分配器。在之前的博客中,我们的这些实现可以有效的避免堆碎片、提高局部性。


分配器的基础概念

分配器,归根结底就是两件事:分配(给出一段未被使用的内存)和释放(把内存归回池子)。在 C++ 里还要注意对齐(alignment)和对象的构造/析构(placementnew、显式destroy)。

常见策略有:Bump(指针上移)分配器Free-list(空闲链表/内存池)Stack(栈)分配器、以及更复杂的TLSF/分级位图等。下面我们通过代码直观对比。


最简单:Bump(线性)分配器 — 启动与临时用例的好朋友

特点:实现极其简单,分配 O(1),不支持释放单个对象(可以一次性重置)。适合启动期分配或者短周期任务。

// bump_allocator.h - 非线程安全,简单演示#include<cstddef>#include<new>#include<cassert>classBumpAllocator{char*start_;char*ptr_;char*end_;public:BumpAllocator(void*buffer,std::size_t size):start_(static_cast<char*>(buffer)),ptr_(start_),end_(start_+size){}void*allocate(std::size_t n,std::size_t align=alignof(std::max_align_t))noexcept{std::size_t space=end_-ptr_;std::uintptr_t p=reinterpret_cast<std::uintptr_t>(ptr_);std::size_t mis=p%align;std::size_t offset=mis?(align-mis):0;if(n+offset>space)returnnullptr;ptr_+=offset;void*res=ptr_;ptr_+=n;returnres;}voidreset()noexcept{ptr_=start_;}};

使用场景:启动时分配所有必要对象,后面不再释放;或临时缓冲池。记住:不能释放单个对象,除非你支持回滚到某个快照点(可以实现“标记/回滚”)。


固定大小内存池(Free-list)

当你有大量相同大小的小对象(例如消息节点、连接对象)时,固定大小内存池非常高效。每个槽(slot)大小固定,释放时把槽 push 回空闲链表。分配/释放都 O(1)。

// simple_pool.h - 单线程示例#include<cstddef>#include<cassert>#include<cstdint>classSimpleFixedPool{structNode{Node*next;};void*buffer_;Node*free_head_;std::size_t slot_size_;std::size_t slot_count_;public:SimpleFixedPool(void*buf,std::size_t slot_size,std::size_t count):buffer_(buf),free_head_(nullptr),slot_size_((slot_size<sizeof(Node*))?sizeof(Node*):slot_size),slot_count_(count){// 初始化空闲链表char*p=static_cast<char*>(buffer_);for(std::size_t i=0;i<slot_count_;++i){Node*n=reinterpret_cast<Node*>(p+i*slot_size_);n->next=free_head_;free_head_=n;}}void*allocate()noexcept{if(!free_head_)returnnullptr;Node*n=free_head_;free_head_=n->next;returnn;}voiddeallocate(void*p)noexcept{Node*n=static_cast<Node*>(p);n->next=free_head_;free_head_=n;}};

要点提示:slot_size应包含对齐与控制信息;线程安全时需要加锁或使用 lock-free 结构(复杂度上升)。内存利用率高,碎片少。


Stack(栈)分配器 — LIFO 场景的神器

当你分配/释放呈 LIFO(后进先出)模式时,栈分配器速度最快,可以释放一系列分配到某个“标记”为止。

// stack_allocator.h - 支持标记回滚classStackAllocator{char*start_;char*top_;char*end_;public:StackAllocator(void*buf,std::size_t size):start_(static_cast<char*>(buf)),top_(start_),end_(start_+size){}void*allocate(std::size_t n,std::size_t align=alignof(std::max_align_t))noexcept{// 类似Bump的对齐处理// ...}// 标记与回滚APIusingMarker=char*;Markermark()noexcept{returntop_;}voidrollback(Marker m)noexcept{top_=m;}};

适用:短生命周期链、任务栈式分配、帧分配(每帧分配,帧结束统一回收)。


用 C++ 风格包装(placement new 与析构)

分配器只提供原始内存;对象的构造/析构工作还是你的任务。示例如下:

#include<new>// placement new// allocate memory for T and constructtemplate<typenameT,typenameAlloc,typename...Args>T*construct_with(Alloc&a,Args&&...args){void*mem=a.allocate(sizeof(T),alignof(T));if(!mem)returnnullptr;returnnew(mem)T(std::forward<Args>(args)...);}// 销毁并归还内存(手动调用析构)template<typenameT,typenameAlloc>voiddestroy_with(Alloc&a,T*obj)noexcept{if(!obj)return;obj->~T();a.deallocate(static_cast<void*>(obj));}

重要:在嵌入式中,禁用异常或在异常敏感代码中使用noexcept的 allocate 是常见实践;因此好多实现返回nullptr而不是抛异常。


如何把自定义分配器和 STL 一起用

标准库的std::allocator接口在老标准中较为笨重。C++17/20 引入了std::pmr::memory_resource(更现代)用于替换默认分配策略。但在嵌入式里往往不启用完整的<memory_resource>,于是你可以自己:

  • 为容器写一个简单的 wrapper,内部使用你的池分配节点。
  • 或实现兼容std::allocator接口的类(需要一堆 typedef 和rebind),然后传给std::vector<T, MyAlloc<T>>

如果构建环境允许,优先考虑std::pmr—— 它语义更清晰,但开销与支持度要看你的平台。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/16 13:56:05

SEW变频器MDX61B0075-5A3-04-00 8279624

孙13665068812SEW MDX61B0075-5A3-04-00 变频器详细介绍 (序列号&#xff1a;8279624) 1. 产品概述与定位 SEW MDX61B0075-5A3-04-00 是 SEW-EURODRIVE 公司生产的 MOVIFIT MDX61B 系列变频器中的一个具体型号。MOVIFIT 系列是 SEW 面向中高端工业应用设计的模块化、多功能变…

作者头像 李华
网站建设 2026/8/28 13:51:16

SEW变频器MDX61B1320-503-04-00 8279748

SEW MDX61B1320-503-04-00 变频器 (8279748) 详细介绍 1. 概述 SEW MDX61B1320-503-04-00 (订货号&#xff1a;8279748) 是德国SEW-EURODRIVE公司生产的 MOVIDRIVE B 系列模块化变频器中的一款特定型号。它代表了该系列中功率等级为 132 kW 的一个配置组合。MOVIDRIVE B 系列…

作者头像 李华
网站建设 2026/8/11 15:46:55

鲜花商城|基于java + vue鲜花商城系统(源码+数据库+文档)

鲜花商城 目录 基于springboot vue鲜花商城系统 一、前言 二、系统功能演示 三、技术选型 四、其他项目参考 五、代码参考 六、测试参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 基于springboot vue鲜花商城系统 一、前言 博主介绍&#xff1a;✌️大…

作者头像 李华
网站建设 2026/8/23 19:48:34

如何选择可编程控制器?可编程控制器分类、性能指标介绍

可编程控制器的作用日渐突出&#xff0c;各大器件中均含有可编程控制器。为增进大家对可编程控制器的了解&#xff0c;本文将对可编程控制器选型技巧、可编程控制器分类以及可编程控制器的技术性能指标进行介绍。如果你对本文内容具有兴趣&#xff0c;不妨继续往下阅读哦。一、…

作者头像 李华
网站建设 2026/8/18 12:10:25

Vue 2.3

一、核心概念 - state 状态 目标&#xff1a;明确如何给仓库提供数据&#xff0c;如何使用仓库的数据 1.提供数据 State提供唯一的公共数据源&#xff0c;所有共享的数据都要统一放到Store中的State中存储。 在state 对象中可以添加我们要共享的数据。 2.使用数据 ①…

作者头像 李华
网站建设 2026/8/26 22:08:15

[Linux]学习笔记系列 -- [drivers][mmc]mmc_sd

title: mmc_sd categories: linuxdriversmmc abbrlink: 64b4b0cf date: 2026-01-16 14:55:15 tags:linuxdriversmmc https://github.com/wdfk-prog/linux-study 文章目录 drivers/mmc/core/sd_ops.cdrivers/mmc/core/sd_uhs2.c SD UHS-II总线管理(SD UHS-II Bus Management) 实…

作者头像 李华