在高性能开发中,内存分配可是个大头问题。默认的 new
和 delete
虽然方便,但效率上不总是让人满意,尤其是频繁分配和释放小块内存时。内存池(Memory Pool)就是解决这种问题的神器。今天就聊聊,怎么自己撸一个 自定义内存池 ,让你的程序飞起来。
简单来说, 内存池 就是提前分配一大块内存,然后按需“切”成小块,分发给需要的对象。用完后,内存直接回收,省掉了频繁调用操作系统的分配函数。就好比你买了一整箱矿泉水,想喝的时候直接拿一瓶,而不是每次都跑超市买。
优点 :
一个简单的内存池可以分为以下几部分:
- 预分配一整块内存
- 分块管理 :把内存分成一块块的“小格子”,每次分配一个格子。
- 回收机制
下面直接用代码实现一个简单的内存池。别担心,代码看起来比你想象的简单。
#include <iostream>
#include <vector>
#include <cstddef> // for std::size_t
class MemoryPool {
public:
MemoryPool(std::size_t blockSize, std::size_t blockCount)
: blockSize_(blockSize), blockCount_(blockCount) {
// 预先分配一大块内存
pool_ = new char[blockSize_ * blockCount_];
// 初始化空闲块链表
for (std::size_t i = 0; i < blockCount_; ++i) {
char* block = pool_ + i * blockSize_;
freeBlocks_.push_back(block);
}
}
~MemoryPool() {
delete[] pool_;
}
void* allocate() {
if (freeBlocks_.empty()) {
std::cerr << “内存池已耗尽!” << std::endl;
return nullptr;
}
// 分配一个空闲块
void* block = freeBlocks_.back();
freeBlocks_.pop_back();
return block;
}
void deallocate(void* block) {
// 回收内存块
freeBlocks_.push_back(static_cast<char*>(block));
}
private:
std::size_t blockSize_; // 每块内存的大小
std::size_t blockCount_; // 内存块数量
char* pool_; // 内存池起始地址
std::vector<char*> freeBlocks_; // 空闲块的链表
};
int main() {
// 创建一个内存池,每块 64 字节,共 10 块
MemoryPool pool(64, 10);
// 分配两块内存
void* p1 = pool.allocate();
void* p2 = pool.allocate();
std::cout << “分配的内存地址:” << p1 << “ 和 ” << p2 << std::endl;
// 释放其中一块内存
pool.deallocate(p1);
// 再分配一块内存,看看是否复用了之前的地址
void* p3 = pool.allocate();
std::cout << “复用的内存地址:” << p3 << std::endl;
return 0;
}
- 构造函数
* `pool_ = new char[blockSize_ * blockCount_]`:一次性分配所有内存。
* 把内存分成小块,放到 `freeBlocks_` 容器里管理。
- 分配函数
* 每次分配一个块,从 `freeBlocks_` 取出一个地址。
* 如果没内存了就报错(实际项目中你可能会加扩展逻辑,自动扩容)。
- 释放函数
* 把用过的内存重新放回 `freeBlocks_`,等下次再用。
- 测试代码
* 分配两块内存,然后释放一块,再次分配时可以看到地址被复用了。
这个内存池的实现非常基础,但在很多场景下已经够用了,比如:
- 游戏开发 :需要频繁创建和销毁敌人、子弹、特效等对象时,内存池能极大提升性能。
- 服务器开发 :处理大量小对象(如网络包、请求数据)的分配和释放。
- 嵌入式系统
- 线程安全问题 :上面的实现没有考虑多线程。如果你的内存池会被多个线程同时访问,记得加锁或者用无锁队列。
- 内存对齐 :某些平台对内存地址有对齐要求,分配的内存块大小最好是 8 的倍数。
- 内存泄漏 :分配的内存块如果忘了释放,会导致内存池资源耗尽,一定要小心管理。
上面实现的内存池虽然简单,但可以进一步优化:
- 支持不同大小的内存块
* 为不同大小的对象准备多个内存池,每个池只分配固定大小的块。
* 比如一个池专门分配 32 字节的块,另一个池分配 64 字节的块。
- 增加内存池动态扩容
- 用链表代替
std::vector
* 直接用链表管理空闲块,性能更高,内存开销更小。
这就是 C++ 自定义内存池的完整实现过程。虽然这种实现称不上“工业级”,但原理已经非常清晰了。很多专业的内存管理库(比如 TCMalloc、JEMalloc)也是基于类似的思想,只不过它们更复杂、更智能。自己动手实现一个内存池,不仅能提升项目性能,还能加深对 C++ 内存管理的理解。