news 2026/3/2 2:43:50

嵌入式现代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/2/23 23:57:53

SEW变频器MDX61B0750-503-04-00 8279705

孙13665068812SEW MDX61B0750-503-04-00 (8279705) 变频器&#xff1a;高性能驱动解决方案的深度剖析 在工业自动化领域&#xff0c;电机驱动系统的性能、效率和可靠性至关重要。SEW-EURODRIVE 作为全球领先的驱动技术供应商&#xff0c;其 MDX 系列变频器广泛应用于各种需要精…

作者头像 李华
网站建设 2026/3/1 12:13:04

软件测试公众号内容热度解析与专业策略

在数字化时代&#xff0c;公众号已成为软件测试从业者获取知识、分享经验的核心平台。内容热度直接决定影响力与用户粘性&#xff0c;但如何精准把握高热度主题&#xff1f;本文基于公众号运营数据与行业实践&#xff0c;解析软件测试领域的热门内容类型&#xff0c;并提供数据…

作者头像 李华
网站建设 2026/2/25 5:36:45

学长亲荐9个降AI率平台 千笔AI助你轻松降AIGC

AI降重工具&#xff1a;让论文更自然&#xff0c;让学术更纯粹 在当前学术环境中&#xff0c;AI生成内容&#xff08;AIGC&#xff09;的广泛应用带来了前所未有的便利&#xff0c;但同时也对论文的原创性和真实性提出了更高要求。对于MBA学生而言&#xff0c;如何在保持专业水…

作者头像 李华
网站建设 2026/2/24 15:59:59

私域浪潮下的选型博弈:微盟和有赞对比深度解析

据《2025中国数字化商业发展报告》显示&#xff0c;超72%的中小企业在数字化转型中首要面临私域工具选型难题&#xff0c;而微盟与有赞作为行业两大核心玩家&#xff0c;常年占据市场关注度TOP2。在私域经营从“流量争夺”转向“精细化运营”的2025年&#xff0c;究竟哪款工具更…

作者头像 李华
网站建设 2026/2/27 19:15:57

angular frequency角频率和frequency频率的区别

angular frequency angular frequency 的标准学术用法与写作注意点(结合你常写的波动入口与数值模拟场景)如下: 1️⃣ 定义(必须写清的一次性说明) angular frequency(角频率) ω=2πf\omega = 2\pi fω=2πf 单位:rads⁻ 📌 论文中第一次出现一定要给定义,否则…

作者头像 李华