内存池是手动预分配大块内存并自行管理小对象生命周期的优化技术,能避免频繁new/delete的系统调用开销,但要求对象大小固定、不跨池混用、且需显式调用placement new和析构函数。

什么是 C++ 里的内存池,它真能避免 new/delete 开销?
内存池不是语言特性,而是你手动预分配一大块 char 数组,再自己管理里面的小对象生命周期。它确实能避开频繁调用 operator new 和 operator delete 的系统调用开销,但前提是:你得保证对象大小固定、不跨池混用、且不依赖构造/析构的自动触发。
常见错误现象:new 慢不是因为语法,而是每次都要查空闲链表、加锁(多线程下)、可能触发 mmap;而内存池把这些摊平到初始化阶段。但如果你只创建几个对象,或者对象大小不一,池反而更重。
- 适用场景:高频创建/销毁同类型小对象(如网络包头、AST 节点、游戏实体组件)
- 不适用场景:对象大小动态变化、需要异常安全构造、或生命周期跨多个模块边界
- 关键点:内存池本身不调用构造函数——你得显式调用
new (ptr) T(args...)(placement new)
怎么写一个最简可用的固定大小内存池?
核心就三件事:预分配内存、维护空闲链表、重载类的 operator new 和 operator delete。别碰 STL 分配器接口,那容易绕进抽象陷阱。
实操建议:
本文档主要讲述的是关于Objective-C手动内存管理的规则;在ios开发中Objective-C 增加了一些新的东西,包括属性和垃圾回收。那么,我们在学习Objective-C之前,最好应该先了解,从前是什么样的,为什么Objective-C 要增加这些支持。有需要的朋友可以下载看看
立即学习“C++免费学习笔记(深入)”;
- 用
std::aligned_storage_t或直接alignas(T) char buffer[N * sizeof(T)]确保对齐 - 空闲链表用指针数组模拟:每个空闲块头部存下一个空闲地址(
reinterpret_cast<void>(ptr)[0] = next;</void>) - 重载时注意:静态
operator new只接收size_t,必须检查sizeof(T)是否匹配池子粒度 - 示例片段:
class Node {<br>public:<br> void* operator new(size_t sz) {<br> if (sz != sizeof(Node)) throw std::bad_alloc();<br> return pool.alloc();<br> }<br> void operator delete(void* p) noexcept { pool.free(p); }<br>private:<br> static MemoryPool<Node> pool;<br>};
为什么重载了 operator new 还会崩?常见踩坑点
崩通常不是内存越界,而是语义错位。比如你忘了 placement new 后没调析构,或释放时没跳过构造标记位。
- 错误现象:
double free或对象状态诡异 → 检查是否重复调用delete,或free()传入了未由alloc()返回的指针 - 错误现象:第一次构造正常,第二次构造后成员值是垃圾 → 忘了在
alloc()里把内存清零,或没调用T的构造函数 - 错误现象:多线程下偶尔 crash → 内存池的
alloc/free不是线程安全的,必须加锁(如std::atomic指针或std::mutex),别指望“无锁”简单实现 - 兼容性影响:如果类有虚函数,
sizeof(T)包含 vptr,池子必须按实际大小对齐,不能只看数据成员
要不要用 boost::pool 或 folly::MemoryPool?
除非你已经卡死在分配性能上,否则先别碰。它们解决的是通用性问题,代价是间接层和额外分支判断。
-
boost::pool默认不支持对齐控制,sizeof(T)偏大时内部碎片明显;它的ordered_malloc在某些负载下比手写链表还慢 -
folly::LruCache类内存池带回收策略,但引入整个 folly 太重;轻量替代可考虑absl::InlinedVector配合栈上缓冲 - 真正该警惕的:用池子掩盖设计问题。比如本该用对象池复用的,却搞成全局单例池,导致不同模块互相干扰
复杂点永远在生命周期管理上——不是怎么分内存,而是谁负责在哪儿调 T::~T(),以及这块内存到底归不归这个池管。这点不厘清,池子建得再快也没用。









