0

0

C++如何实现简单的内存池_C++自定义内存分配器提升性能【高级】

尼克

尼克

发布时间:2026-02-05 13:05:02

|

825人浏览过

|

来源于php中文网

原创

内存池通过预分配大块内存并用链表管理空闲块,避免malloc/new的频繁系统调用、锁竞争和元数据开销;FixedBlockPool以固定大小块+单链表实现O(1)分配/释放,需注意对象大小一致、生命周期可控及分配器成对使用。

c++如何实现简单的内存池_c++自定义内存分配器提升性能【高级】

为什么 mallocnew 在高频小对象场景下会拖慢性能

频繁调用 mallocnew 分配小块内存(比如几十字节的节点),会导致堆管理器反复拆分/合并空闲块、维护元数据、加锁(多线程下),甚至触发系统调用。实际测下来,分配一个 32 字节对象可能比构造对象本身还慢 5–10 倍。

内存池的核心思路是:一次性向系统申请一大块内存(比如 64KB),内部用链表或位图管理空闲块,allocate() 只做指针偏移 + 链表摘除,deallocate() 只做链表插入 —— 全部在用户态完成,无锁(单线程)或轻量锁(多线程)。

  • 适用场景:std::list 节点、事件循环中的 task 结构、游戏里大量 particle 对象
  • 不适用场景:对象大小差异大(如同时有 16B 和 2KB 对象)、生命周期不规则(无法批量回收)
  • 关键约束:必须成对使用自定义分配器,不能混用 new/delete 和池内指针

如何写一个线程安全的固定大小内存池(FixedBlockPool

固定块池最简单也最常用。假设你要为 struct Node { int x; Node* next; }; 提供分配器,大小固定为 16 字节(含对齐)。

核心结构体只需三个成员:m_block_size(块大小)、m_free_list(指向空闲块的 char* 单链表)、m_blocks(原始大内存块首地址)。所有操作围绕链表头进行:

立即学习C++免费学习笔记(深入)”;

超能文献
超能文献

超能文献是一款革命性的AI驱动医学文献搜索引擎。

下载
class FixedBlockPool {
    size_t m_block_size;
    char* m_blocks;
    char* m_free_list;
public:
    FixedBlockPool(size_t block_sz, size_t n_blocks) 
        : m_block_size{block_sz}, m_free_list{nullptr} {
        const size_t total = n_blocks * block_sz;
        m_blocks = static_cast(::operator new(total));
        // 构建初始空闲链表:每个块头存下一个空闲块地址
        for (size_t i = 0; i < n_blocks; ++i) {
            char* p = m_blocks + i * block_sz;
            *reinterpret_cast(p) = m_free_list;
            m_free_list = p;
        }
    }
void* allocate() {
    if (!m_free_list) return nullptr;
    char* p = m_free_list;
    m_free_list = *reinterpret_cast(p); // 摘链
    return p;
}

void deallocate(void* p) {
    if (!p) return;
    *reinterpret_cast(p) = m_free_list; // 入链
    m_free_list = static_cast(p);
}

};

  • 注意:这里没加锁,多线程需用 std::atomic 替代裸指针,allocate() 改用 compare_exchange_weak
  • ::operator new 绕过 new_handler,避免递归;别用 malloc,它不保证对齐
  • 释放时不做校验 —— 若传入非法地址,行为未定义;生产环境可加 assert(p >= m_blocks && p

怎么让 std::vectorstd::unordered_map 用上你的内存池

C++ 容器支持模板参数 Allocator,但不是直接传池对象,而是传满足 Allocator 要求的类型。你需要封装一个符合标准的分配器类,例如 PoolAllocator

template 
class PoolAllocator {
    static inline FixedBlockPool* s_pool = nullptr;
public:
    using value_type = T;
    PoolAllocator() = default;
    template  constexpr PoolAllocator(const PoolAllocator&) noexcept {}
T* allocate(size_t n) {
    if (n != 1) throw std::bad_alloc(); // 固定块池只支持单对象
    if (!s_pool) s_pool = new FixedBlockPool(alignof(T), sizeof(T), 1024);
    return static_cast(s_pool->allocate());
}

void deallocate(T* p, size_t n) {
    if (n == 1) s_pool->deallocate(p);
}

};

然后这样使用:

std::vector> vec;
std::unordered_map, std::equal_to<>, 
                   PoolAllocator>> map;
  • 注意:不同 T 类型必须对应不同池实例(intNode 不能共用同一 FixedBlockPool
  • std::allocator_traits 会自动处理 construct/destroy,你不用重写它们
  • 容器析构时不会自动销毁池 —— 记得在程序退出前手动 delete s_pool,否则泄漏

容易被忽略的对齐和异常安全细节

内存池返回的指针必须满足 T 的对齐要求(alignof(T)),否则 new 表达式或 std::construct_at 可能崩溃。固定块池里,块大小必须是 alignof(T) 的整数倍,且首地址本身也要对齐。

  • 申请大内存时用 aligned_alloc(C++17)或 _aligned_malloc(Windows),而不是 malloc
  • allocate() 失败(m_free_list == nullptr),应抛出 std::bad_alloc,而非返回 nullptr —— 标准容器不检查返回值,会直接解引用空指针
  • 不要在 deallocate() 里调用 deletefree;也不要试图在池中 deletenew 分配的对象 —— 这是未定义行为
  • 调试时可给每块加 magic number(如 0xDEADBEEF),在 allocate/deallocate 里校验,快速定位 double-free 或越界写

真正难的不是写出来,而是确保所有路径都走池、所有析构都正确触发、所有线程竞争都被覆盖到。先从单线程 + 一种类型跑通,再加锁,再扩展多类型支持。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

282

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

193

2025.07.04

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

585

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

550

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

153

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

204

2025.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

153

2025.08.29

C++中int、float和double的区别
C++中int、float和double的区别

本专题整合了c++中int和double的区别,阅读专题下面的文章了解更多详细内容。

104

2025.10.23

java连接字符串方法汇总
java连接字符串方法汇总

本专题整合了java连接字符串教程合集,阅读专题下面的文章了解更多详细操作。

7

2026.02.05

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 6.9万人学习

前端基础到实战(HTML5+CSS3+ES6+NPM)
前端基础到实战(HTML5+CSS3+ES6+NPM)

共162课时 | 19.4万人学习

第二十二期_前端开发
第二十二期_前端开发

共119课时 | 12.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号