直接new/delete在高频短生命周期场景下性能差,因系统分配器需加锁、查链表、合并分割块;内存池通过预分配大块内存并用自由链表管理空闲块,避免系统调用,提升分配/释放效率。

为什么直接 new/delete 不够用
频繁调用 new 和 delete 会产生大量小内存块碎片,且系统级分配器(如 malloc)每次都要加锁、查空闲链表、合并分割块——在高频短生命周期对象场景(比如游戏帧内粒子、网络包解析临时结构)下,这会明显拖慢性能。内存池的核心思路是:一次性向系统申请一大块内存,内部用链表或数组管理空闲块,分配/释放变成指针偏移+链表操作,完全避开系统调用。
如何用固定大小块实现最简内存池
适用于对象大小一致的场景(例如全是 struct Packet),实现成本最低、无碎片、无额外元数据开销:
- 池子初始化时用
operator new 分配一块连续内存,按对象大小切分成若干等长槽位
- 维护一个指向首个空闲槽的
char<em></em>(即“自由链表头”),每个空闲槽的前 sizeof(void) 字节存下一个空闲槽地址
-
allocate() 返回当前头指针,并把头指针更新为该槽里存的下一个地址
-
deallocate(ptr) 把 ptr 写入当前头指针位置,再让头指针指向 ptr
class FixedPool {
char* memory_;
size_t block_size_;
size_t capacity_;
char* free_list_;
<p>public:
FixedPool(size_t n, size_t sz) : block<em>size</em>(sz), capacity<em>(n) {
memory</em> = static_cast<char<em>>(operator new(n </em> sz));
free<em>list</em> = memory_;
// 构建单向空闲链表:每个空闲块开头存下一个空闲块地址
for (size<em>t i = 0; i < n - 1; ++i) {
char* block = memory</em> + i <em> sz;
</em>reinterpret_cast<char*<em>>(block) = block + sz;
}
</em>reinterpret<em>cast<char**>(memory</em> + (n-1)*sz) = nullptr;
}</p><pre class='brush:php;toolbar:false;'>void* allocate() {
if (!free_list_) return nullptr;
void* p = free_list_;
free_list_ = *reinterpret_cast<char**>(free_list_);
return p;
}
void deallocate(void* p) {
*reinterpret_cast<char**>(p) = free_list_;
free_list_ = static_cast<char*>(p);
}
~FixedPool() { operator delete(memory_); }
struct Packet),实现成本最低、无碎片、无额外元数据开销:
- 池子初始化时用
operator new分配一块连续内存,按对象大小切分成若干等长槽位 - 维护一个指向首个空闲槽的
char<em></em>(即“自由链表头”),每个空闲槽的前 sizeof(void) 字节存下一个空闲槽地址 -
allocate()返回当前头指针,并把头指针更新为该槽里存的下一个地址 -
deallocate(ptr)把ptr写入当前头指针位置,再让头指针指向ptr
class FixedPool {
char* memory_;
size_t block_size_;
size_t capacity_;
char* free_list_;
<p>public:
FixedPool(size_t n, size_t sz) : block<em>size</em>(sz), capacity<em>(n) {
memory</em> = static_cast<char<em>>(operator new(n </em> sz));
free<em>list</em> = memory_;
// 构建单向空闲链表:每个空闲块开头存下一个空闲块地址
for (size<em>t i = 0; i < n - 1; ++i) {
char* block = memory</em> + i <em> sz;
</em>reinterpret_cast<char*<em>>(block) = block + sz;
}
</em>reinterpret<em>cast<char**>(memory</em> + (n-1)*sz) = nullptr;
}</p><pre class='brush:php;toolbar:false;'>void* allocate() {
if (!free_list_) return nullptr;
void* p = free_list_;
free_list_ = *reinterpret_cast<char**>(free_list_);
return p;
}
void deallocate(void* p) {
*reinterpret_cast<char**>(p) = free_list_;
free_list_ = static_cast<char*>(p);
}
~FixedPool() { operator delete(memory_); }};
注意:这个实现不调用构造函数/析构函数,使用时需配合 placement new 和显式析构。
释放时忘记调用析构函数是最大坑
内存池只管内存,不管对象生命周期。常见错误:
- 直接
pool.allocate() 后用 new (ptr) T{...} 构造,但 pool.deallocate(ptr) 前没调用 ptr->~T()
- 多态对象(含虚函数)被销毁后,若后续又从池中分配同一地址并构造新对象,虚表指针可能残留旧值,引发未定义行为
pool.allocate() 后用 new (ptr) T{...} 构造,但 pool.deallocate(ptr) 前没调用 ptr->~T()
建议做法:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 将内存池封装进带类型信息的模板类,如
ObjectPool<t></t>,在deallocate中自动调用static_cast<T*>(ptr)->~T() - 或强制使用者遵守协议:所有通过池分配的对象,必须显式析构后再归还
std::pmr::memory_resource 能否直接替代手写池
可以,但要注意适用边界:
-
std::pmr::monotonic_buffer_resource 是单向增长、不回收的,适合“一帧内分配、整帧结束统一释放”的场景,不能做细粒度 deallocate
-
std::pmr::unsynchronized_pool_resource 更接近本文写的固定块池,但它内部做了对齐、多尺寸块管理、跨线程隔离等,启动开销和内存占用更高;调试时堆栈里看到的是 std::pmr::polymorphic_allocator::allocate,而非你自己的函数名,排查问题稍绕
std::pmr::monotonic_buffer_resource 是单向增长、不回收的,适合“一帧内分配、整帧结束统一释放”的场景,不能做细粒度 deallocate
std::pmr::unsynchronized_pool_resource 更接近本文写的固定块池,但它内部做了对齐、多尺寸块管理、跨线程隔离等,启动开销和内存占用更高;调试时堆栈里看到的是 std::pmr::polymorphic_allocator::allocate,而非你自己的函数名,排查问题稍绕如果只需要一种固定大小、确定性极高的分配行为,手写池更轻量、更可控,也更容易嵌入到无 STL 环境(如嵌入式、内核模块)。
立即学习“C++免费学习笔记(深入)”;
真正难的不是分配逻辑本身,而是决定什么时候该回收整个池、如何与 RAII 容器(如 std::vector 配合 std::pmr::polymorphic_allocator)协作,以及是否允许不同大小对象混用——这些会让简单池迅速膨胀成完整内存管理器。

















