固定步长的内存池分配器是针对特定大小内存块设计的分配工具,通过预分配大块内存并拆分为固定大小的单元进行管理,减少系统调用带来的开销,同时配合内存对齐提升访问效率,优化块管理的逻辑。

核心设计思路
固定步长内存池的核心是将预分配的大块内存拆分为多个大小相同的内存块,通过空闲链表管理可分配的块,分配时从链表头部取块,回收时将块插回链表。同时需要处理内存对齐,确保分配的内存块地址符合CPU访问要求,提升访问速度。
内存对齐规则
通常内存对齐要求地址是2的幂次倍数,比如8字节、16字节对齐。对于固定步长的内存池,步长本身需要是最大对齐值的倍数,同时每个内存块的起始地址也需要满足对齐要求。我们可以通过计算对齐后的步长,以及调整内存块起始地址来实现对齐。
块管理结构
每个空闲内存块的前几个字节可以用来存储下一个空闲块的地址,形成空闲链表。当内存块被分配后,这部分空间会被用户使用,回收时再重新写入下一个空闲块的地址。这种方式不需要额外的结构存储块信息,节省内存开销。
完整实现代码
以下是固定步长内存池分配器的完整实现,包含内存对齐处理、块分配、块回收、内存池扩容等功能:
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
class FixedStepMemoryPool {
private:
// 默认对齐值,可根据需求修改
static constexpr size_t DEFAULT_ALIGN = 8;
// 每次扩容分配的内存块数量
static constexpr size_t EXPAND_BLOCK_COUNT = 16;
size_t step_size; // 用户请求的步长
size_t aligned_step; // 对齐后的步长
size_t block_size; // 实际每个块的大小,至少为指针大小
char* pool_start; // 内存池起始地址
char* pool_end; // 内存池结束地址
void* free_list_head; // 空闲链表头
// 计算对齐值,返回大于等于val的最小的align的倍数
size_t align_size(size_t val, size_t align) const {
return (val + align - 1) & ~(align - 1);
}
// 分配新的大块内存并拆分为块加入空闲链表
void expand_pool() {
size_t alloc_size = aligned_step * EXPAND_BLOCK_COUNT;
char* new_block = static_cast<char*>(malloc(alloc_size));
if (!new_block) {
throw std::bad_alloc();
}
// 初始化新分配的内存块,加入空闲链表
char* current = new_block;
for (size_t i = 0; i < EXPAND_BLOCK_COUNT; ++i) {
// 将当前块的前几个字节存储下一个块的地址
*reinterpret_cast<void**>(current) = free_list_head;
free_list_head = current;
current += aligned_step;
}
// 更新内存池的起止地址(仅记录初始分配的大块,方便后续释放)
if (!pool_start) {
pool_start = new_block;
pool_end = new_block + alloc_size;
}
}
public:
// 构造函数,传入用户需要的步长
explicit FixedStepMemoryPool(size_t step)
: step_size(step), pool_start(nullptr), pool_end(nullptr), free_list_head(nullptr) {
// 计算对齐后的步长,至少为指针大小
aligned_step = align_size(step_size, DEFAULT_ALIGN);
block_size = align_size(step_size > sizeof(void*) ? step_size : sizeof(void*), DEFAULT_ALIGN);
if (aligned_step != block_size) {
aligned_step = block_size;
}
// 初始扩容一次,预分配内存
expand_pool();
}
// 析构函数,释放所有预分配的内存
~FixedStepMemoryPool() {
if (pool_start) {
free(pool_start);
pool_start = nullptr;
pool_end = nullptr;
free_list_head = nullptr;
}
}
// 分配内存块
void* allocate() {
if (!free_list_head) {
expand_pool();
}
// 从空闲链表头部取块
void* block = free_list_head;
free_list_head = *reinterpret_cast<void**>(free_list_head);
return block;
}
// 回收内存块
void deallocate(void* ptr) {
if (!ptr) {
return;
}
// 将回收的块插回空闲链表头部
*reinterpret_cast<void**>(ptr) = free_list_head;
free_list_head = ptr;
}
// 获取对齐后的步长
size_t get_aligned_step() const {
return aligned_step;
}
// 禁止拷贝构造和赋值
FixedStepMemoryPool(const FixedStepMemoryPool&) = delete;
FixedStepMemoryPool& operator=(const FixedStepMemoryPool&) = delete;
};
// 测试示例
int main() {
// 创建步长为12字节的内存池,会对齐到16字节
FixedStepMemoryPool pool(12);
printf("对齐后的步长:%zun", pool.get_aligned_step());
// 分配3个内存块
void* block1 = pool.allocate();
void* block2 = pool.allocate();
void* block3 = pool.allocate();
printf("分配到的块地址:%p, %p, %pn", block1, block2, block3);
// 回收内存块
pool.deallocate(block1);
pool.deallocate(block2);
pool.deallocate(block3);
return 0;
}
关键逻辑说明
上述实现中,内存对齐通过align_size函数实现,确保对齐后的步长是默认对齐值的倍数。空闲链表通过每个内存块的前sizeof(void*)字节存储下一个空闲块地址,不需要额外的管理结构。
扩容时每次分配EXPAND_BLOCK_COUNT个块的内存,拆分为单个块后加入空闲链表,避免频繁扩容。析构时仅释放初始分配的大块内存,因为所有块都来自这些大块,不需要逐个释放。
使用注意事项
- 该内存池仅适合分配固定大小的内存块,不适合变长内存分配场景。
- 如果用户需要自定义对齐值,可以修改
DEFAULT_ALIGN常量,或者增加构造函数的对齐参数。 - 分配的内存块需要调用
deallocate回收,否则会造成内存泄漏。 - 该实现没有做线程安全处理,多线程场景下需要额外加锁保护空闲链表操作。