一、思路
首先开辟一个大块的连续内存,然后每次使用从大块内存中取出固定长度的小块内存。总体分为两部分:New和Delete。
New分为两种情况
①使用过的小块内存被释放了,我们使用链表将其管理起来,当再次调用New函数的时候,我们优先使用已经被释放的小块内存。
②释放列表为空,这时候又分为两种情况:
Ⅰ第一次调用New:直接使用malloc开辟一大块内存,然后再在这一大块内存中切割出一个小块内存,供人使用并返回。
Ⅱ大块内存剩余部分大于将要切割出的小块内存:直接在大块内存中切割出一小块内存并返回指针。
Ⅲ大块内存剩余部分小于将要切割出的小块内存:舍弃剩余部分,再开辟一个大块内存。
delete也分为两种情况
①释放链表为空:将被释放的小块内存的 next 指针置为
nullptr,并将链表头指向该块。②释放链表非空:将被释放的小块内存作为新的链表头,它的 next 指向原链表头(头插)。
二、实现
1.私有成员
cpp
private:
char* _memory = nullptr; //指向大块内存的指针,因为char是一个字节,好切
size_t _remainBytes = 0; //大块内存在切分过程中剩余字节数
void* deletelist = nullptr; //指向已经删除的内存空间链表
①成员含义
Ⅰ _memory:指向大块内存
Ⅱ _remainBytes:大块内存在切分过程中剩余的字节数
Ⅲ _deletelist:指向释放链表的表头
②类型说明
Ⅰ memory:使用char*是为了按字节精确切分大块内存
Ⅱ deletelist:为了做成通用空闲链表,不绑定具体类型
2.New函数实现
①首先实现释放链表为空的情况
cpp
if (_remainBytes < sizeof(T))
{
//开辟大块内存
_remainBytes = 128 * 1024; //开一个128kb的空间
_memory = (char*)malloc(_remianBytes);
}
//将小块内存切分出去,memory指向下一块小块内存,remainBytes减少sizeof(T)
T* obj = _memccpy;
_memory += sizeof(T);
_remainBytes -= sizeof(T);
return obj;
思路:当剩余部分不足以在切割出一个小块内存的时候,这种情况要么是第一次使用New要么就是空间用完了,这时候直接使用malloc开辟一块大内存,然后再移动memory并修改剩余字节数;如果当前剩余空间还能够切割出小块内存,则直接不走开辟大块内存的过程,直接走下边的逻辑即可。这个过程直接将释放链表为空中的三种情况融合到了一段代码中。
②释放链表不为空
cpp
if(deletelist != nullptr)
{
//思路:将表头给obj,将表头的next给deletlist,返回obj
void* next = *((void**)_deletelist);
obj = (T*)_deletelist;
_deletelist = next;
return obj;
}

思路:将表头给obj,将表头的next给deletelist,返回obj
注意:next获取的为什么这么写后边详细讲。
3.Delete函数实现
思路:有两种情况,链表为空和非空,由于链表刚开始就指向空,所以不管是链表为空还是非空,只需要将新的结点头插到链表中即可,所以两种情况在实现上的代码是相同的。
cpp
void Delete(T* obj)
{
*(void**)obj = _deletelist;
_deletelist = obj;
}
为什么要使用*(void**)进行类型转换?
答:把
obj这块内存的前几个字节,当成一个 "指针变量" 来用,存链表的 next 指针。
(void**)obj的意思是:把 obj 这块内存,当成一个用来存放 "指针" 的变量
三、优化
1.如果T是char类型或者占的字节大小是小于我们需要的在里边插入的指针的大小,怎么办?
我们的思路是在分割内存的时候直接给这块小内存块分一块等于指针大小的内存块,比如你是char类型,按原来的逻辑需要给你分一块1字节的内存块,但是我现在给你一块指针大小的内存块,以确保你可以存储下一个指针。
实现:只需更改new函数中切割内存块的过程即可。
cppif (_remainBytes < sizeof(T)) { //开辟大块内存 _remainBytes = 128 * 1024; //开一个128kb的空间 _memory = (char*)malloc(_remianBytes); } //将小块内存切分出去,memory指向下一块小块内存,remainBytes减少sizeof(T) T* obj = _memccpy; size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T); _memory += objSize; _remainBytes -= objSize;
2.显示析构删除结点
cpp
void Delete(T* obj)
{
obj->~T();
*(void**)obj = _deletelist;
_deletelist = obj;
}
3.定位new
普通 new = 开内存 + 调用构造函数
定位 new = 只调用构造函数,不开辟新内存
我们内存池,只是通过
malloc/ 指针偏移拿到了一块裸内存。 这块内存:
- 地址合法、可以读写
- 但没有初始化对象、没有执行构造函数、成员都是随机脏值
new(obj) T();的作用: 在 已经分配好的 obj 内存地址上,手动执行一遍 T 的构造函数,把裸内存初始化为合法的 T 对象。
为什么必须写这一行?不写会崩 / 出 bug
裸内存无对象状态
malloc只会申请原始内存,不会构造对象,类的成员变量都是随机脏数据。析构会出问题 你的
Delete函数调用了obj->~T()析构函数。 如果没有构造就直接析构 ,属于未定义行为,大概率程序崩溃。自定义类型必崩 如果是
int这种基础类型,偶尔能跑; 如果是string、自定义结构体、带资源的类,百分百内存泄漏 + 崩溃。
和普通new的区别
cppT* p = new T(); // 1. 堆上开辟内存 2. 构造对象 new(obj) T(); // 1. 不开辟内存 2. 仅构造对象
cpp
else
{
if (_remainBytes < sizeof(T))
{
//开辟大块内存
_remainBytes = 128 * 1024; //开一个128kb的空间
_memory = (char*)malloc(_remainBytes);
if (_memory == nullptr)
throw std::bad_alloc();
}
//将小块内存切分出去,memory指向下一块小块内存,remainBytes减少sizeof(T)
obj = (T*)_memory;
size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
_memory += objSize;
_remainBytes -= objSize;
}
new(obj)T;
return obj;
}
四、内存池的意义
控制台测试 new / 内存池效果一模一样,我为什么要费这么大劲写内存池?直接 int* 定义、直接 new 不行吗?
- 控制台场景:确实没用
如果只是:
创建一两个 int 对象
简单赋值、打印
程序运行一秒就结束
new 完全够用,内存池毫无优势。
- 真实工程场景:new 根本扛不住
new / malloc 是操作系统全局堆,存在三个致命问题:
速度慢:每次 new 都是系统调用、堆查找、加锁解锁
内存碎片严重:频繁小块分配释放,导致内存碎片化,大内存申请失败
高并发锁竞争:全局堆多线程争抢锁,性能暴跌
- 内存池的核心优势(真正的设计意义)
预分配大块内存:只调用一次 malloc,后续分配只是指针挪动 O(1)
复用释放的内存:自由链表缓存释放的对象,无需反复系统调用
无内存碎片:统一大块管理,不会产生细碎碎片
高性能高并发:用户态内存管理,无全局锁竞争
两个关键类型的设计精髓?
- 为什么大块内存用 char*?
char 占 1 字节,指针加减精准按字节移动,完美实现任意大小内存切割。
如果用 int*/void* 都无法精准分步切割内存。
- 为什么自由链表要用 void** 强转?
*(void**)obj = _deletelist;
(void**)obj:把对象内存起始地址, reinterpret 为「指针变量的地址」
*解引用:操作这块内存,写入 next 指针核心思想:利用已释放对象自身的内存,存储链表指针,零额外开销
总结
内存池不是为控制台小demo设计的,是为高频率、大批量、长期运行的程序设计的(游戏、服务器、后端服务)。
直接 new 写代码简单,但性能差、碎片多、并发弱。
定长内存池核心:预分配大块 + 指针切割 + 自由链表复用。
手写内存池能彻底搞懂:内存对齐、指针强转、自由链表、对象构造析构、内存碎片等底层核心知识。
五、代码展示
cpp
#pragma once
#include <iostream>
template <class T> //类型
class ObjectPoll
{
public:
T* New()
{
T* obj = nullptr;
if(_deletelist != nullptr)
{
//思路:将表头给obj,将表头的next给deletlist,返回表头
void* next = *((void**)_deletelist);
obj = (T*)_deletelist;
_deletelist = next;
}
else
{
if (_remainBytes < sizeof(T))
{
//开辟大块内存
_remainBytes = 128 * 1024; //开一个128kb的空间
_memory = (char*)malloc(_remainBytes);
if (_memory == nullptr)
throw std::bad_alloc();
}
//将小块内存切分出去,memory指向下一块小块内存,remainBytes减少sizeof(T)
obj = (T*)_memory;
size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
_memory += objSize;
_remainBytes -= objSize;
}
new(obj)T;
return obj;
}
//思路:头插
void Delete(T* obj)
{
obj->~T();
*(void**)obj = _deletelist;
_deletelist = obj;
}
private:
char* _memory = nullptr; //指向大块内存的指针,因为char是一个字节,好切
size_t _remainBytes = 0; //大块内存在切分过程中剩余字节数
void* _deletelist = nullptr; //指向已经删除的内存空间链表
};