⚾️概述
是什么:是对内存块的管理组件
作用:
对整块的内存进行集中管理。
避免了频繁向OS调用malloc
避免了内存碎片
🥏C++ STL内存池实现
在C++STL库中,采用哈希表和定长内存池结合的方式实现了一个内存池。具体如下
构造多个定长内存池,以一个固定的对齐数进行对齐(例如以8字节进行对齐),第一个定长内存池的内存对象大小为8(至少得能保证无论在64位还是32位系统下都可以保存下一个指针类型),第二个内存池对象大小为16…最后一个内存池对象大小为128byte,当申请的内存大小超过128字节时,通过二级空间配置器申请(直接从内存中申请)。
构造一个哈希表,将不同大小的内存对象挂在哈希表中。如下图: 申请内存:加入要申请的内存大小为8字节直接在index = 0处分配一块内存,当然申请的内存小于8字节时也会直接分配8字节的内存。当Free_list[index]为nullptr时从内存中申请一块内存,切割成固定大小‘挂在"Free_list[index]位置。
释放内存:根据内存对象大小,计算index在插入到哈希表中的index位置。
🥎源码实现
ps:内存池难点在于对指针的灵活应用!!! (线程池在于线程安全性)
ps:若无法理解==*(char**)==的含义,请阅读本站《对指针的回炉重造》
🪃1.实现定长的内存池
可随时回收,但只能定长
#include <stdio.h>
#include <stdlib.h>
//
#define MEM_PAGE_SIZE 4096
typedef struct mempool_s {
int block_size;
int free_count;
char *free_ptr;
char *mem;
} mempool_t;
int mp_init(mempool_t *m, int size) {
if (!m) return -1;
if (size < 16) size = 16;
m->block_size = size;
m->mem = (char *)malloc(MEM_PAGE_SIZE);
if (!m->mem) return -1;
m->free_ptr = m->mem;
m->free_count = MEM_PAGE_SIZE / size;
int i = 0;
char *ptr = m->free_ptr;
for (i = 0;i < m->free_count;i ++) {
*(char **)ptr = ptr + size;
ptr += size;
}
*(char **)ptr = NULL;
return 0;
}
void mp_dest(mempool_t *m) {
if (!m || !m->mem) return ;
free(m->mem);
}
void *mp_alloc(mempool_t *m) {
if (!m || m->free_count == 0) return NULL;
void *ptr = m->free_ptr;
m->free_ptr = *(char **)ptr;
m->free_count --;
return ptr;
}
void mp_free(mempool_t *m, void *ptr) {
//让ptr指向的内存块node0指向free_ptr
//再让free_ptr指向node0
//实际上就是让node0插入
*(char **)ptr = m->free_ptr;
m->free_ptr = (char *)ptr;
m->free_count ++;
}
int main() {
mempool_t m;
mp_init(&m, 32);
void *p1 = mp_alloc(&m);
printf("1: mp_alloc: %p\n", p1);
void *p2 = mp_alloc(&m);
printf("2: mp_alloc: %p\n", p2);
void *p3 = mp_alloc(&m);
printf("3: mp_alloc: %p\n", p3);
void *p4 = mp_alloc(&m);
printf("4: mp_alloc: %p\n", p4);
mp_free(&m, p2);
void *p5 = mp_alloc(&m);
printf("5: mp_alloc: %p\n", p5);
return 0;
}
//答案
1: mp_alloc: 0x138008800
2: mp_alloc: 0x138008820
3: mp_alloc: 0x138008840
4: mp_alloc: 0x138008860
5: mp_alloc: 0x138008820
🛼2.变长内存池
可以变长分配,但只能统一回收
#include <stdio.h>
#include <stdlib.h>
#define MEM_PAGE_SIZE 4096
typedef struct mp_node_s {
char *free_ptr;
char *end;
struct mp_node_s *next;
} mp_node_t;
typedef struct mp_pool_s {
struct mp_node_s *first;
struct mp_node_s *current;
int max; // page size
} mp_pool_t;
int mp_init(mp_pool_t *m, int size) {
if (!m) return -1;
void *addr = malloc(size); // 4096
mp_node_t *node = (mp_node_t*)addr;
node->free_ptr = (char*)addr + sizeof(mp_node_t);
node->end = (char*)addr + size;
node->next = NULL;
m->first = node;
m->current = node;
m->max = size;
return 0;
}
void mp_dest(mp_pool_t *m) {
if (!m) return ;
while (!m->first) {
void *addr = m->first;
mp_node_t *node = (mp_node_t*)addr;
m->first = node->next;
free(addr);
}
return ;
}
void *mp_alloc(mp_pool_t *m, int size) { //size < (4096-sizeof(mp_node_t))
void *addr = m->current;
mp_node_t *node = (mp_node_t*)addr;
do {
if (size <= (node->end - node->free_ptr)) { //
char *ptr = node->free_ptr;
node->free_ptr += size;
return ptr;
}
node = node->next;
} while (node);
// new node
addr = malloc(m->max); // 4096
node = (mp_node_t*)addr;
node->free_ptr = (char*)addr + sizeof(mp_node_t);
node->end = (char*)addr + m->max;
node->next = m->current;
m->current = node;
char *ptr = node->free_ptr;
node->free_ptr += size;
return ptr;
}
void mp_free(mp_pool_t *m, void *ptr) {
}
int main() {
mp_pool_t m;
mp_init(&m, MEM_PAGE_SIZE);
void *p1 = mp_alloc(&m, 16);
printf("1: mp_alloc: %p\n", p1);
void *p2 = mp_alloc(&m, 32);
printf("2: mp_alloc: %p\n", p2);
void *p3 = mp_alloc(&m, 64);
printf("3: mp_alloc: %p\n", p3);
void *p4 = mp_alloc(&m, 128);
printf("4: mp_alloc: %p\n", p4);
void *p5 = mp_alloc(&m, 256);
printf("5: mp_alloc: %p\n", p5);
mp_dest(&m);
}
#运行结果
1: mp_alloc: 0x137008818
2: mp_alloc: 0x137008828
3: mp_alloc: 0x137008848
4: mp_alloc: 0x137008888
5: mp_alloc: 0x137008908
🛹3. 双链表内存池实现
整体设计
内存池结构
两个链表,RequestMemory和ReleaseMemory。
RequestMemory链表存储的是使用new或者malloc从物理内存申请的还没有被使用的内存块,是一个个的memNode节点。
ReleaseMemory链表存储的是使用完释放回来的固定大小的内存块。
申请内存
先在ReleaseMemory找,如果有内存则直接pop使用
ReleaseMemory为nullptr时,在RequestMemory中找。
RequestMemory的头节点表示的是新申请的,申请内存时只需要在头结点中找,判断头结点的useCount和sumCount是否相等。当useCount等于sumCount时表示已经用完了,就需要去物理内存中申请,否则直接从表头push一块。
去物理内存申请内存时,申请的大小是上一次申请内存块大小的二倍,并将申请的内存块push到RequestMemory头部。
释放内存
释放内存时,直接将要释放的内存push到ReleaseMemory的头部即可。
#include <iostream>
using namespace std;
#define MEM_PAGE_SIZE 4096
class MemPool {
public:
//这个是requestmemory的节点
struct MemBlock {
// 这条一定要放在第一位,为了后面双指针**
void *ptr; //block内存的首地址
MemBlock *next;//
size_t _objNum;//内存块对象的数量
size_t _useCount;
MemBlock(size_t num) : _objNum(num), next(nullptr), _useCount(0) {
ptr = malloc(_objNum * _size);
}
};
MemPool() {
//初始化的时候应该初始化Requestmem?
_maxSize = 4096;
ReleaseMemory = nullptr;
RequestMemory = new MemBlock(MEM_PAGE_SIZE);
cout <<"调用MemPool构造函数"<< endl;
}
~MemPool() {
cout << "调用mempool析构函数~()" << endl;
//释放的时候两个链表一起释放
//先释放RequestMemory
MemBlock *tmp = RequestMemory;
while (tmp != nullptr) {
MemBlock *del = tmp;
tmp = tmp->next;
// del->~MemBlock();//析构block
delete del; //会自动调用~memblock()
}
//todo:再释放ReleaseMemory?需要释放这个吗?这个全部都是void*的内存
}
void* allocate() {
//分配内存先从releaseMemory找
if (ReleaseMemory != nullptr) {
void *ptr = *(void**)RequestMemory;
ReleaseMemory = *(void**)ReleaseMemory; //ReleaseMemory前几个字节存储的是下一个node的地址
return ptr;
}
//releasemem为空,则去requestmem中拿,如果拿不到就申请两倍
if (RequestMemory->_objNum == RequestMemory->_useCount) {
int new_objNum = RequestMemory->_useCount * 2;
RequestMemory->_objNum = new_objNum > _maxSize ? _maxSize : new_objNum;
MemBlock *newBlock = new MemBlock(RequestMemory->_objNum);
//把这个newblock挂到RequestMem头部
newBlock->next = RequestMemory;
RequestMemory = newBlock;
//把usecount重置
RequestMemory->_useCount = 0;
}
//分配内存
void *ptr = (char *)RequestMemory + RequestMemory->_useCount * _size;
RequestMemory->_useCount++;
return ptr;
}
void deallocate(void* p) {
if (p) {
//把释放的内存放到releaseMemory中去
*(void**)p = ReleaseMemory;
ReleaseMemory = p;
}
}
protected:
void* ReleaseMemory = NULL; //一开始releasemem是空的
//为什么是静态成员变量?只在本文件可见!
static size_t _size; //单个内存对象的大小
size_t _maxSize;//最大的block的内存大小
MemBlock* RequestMemory;
private:
};
size_t MemPool::_size = 16; //单个内存对象的大小设置为8bytes
int main() {
MemPool memPool;
void* p = memPool.allocate();
printf("p:malloc: %p\n", p);
void* p2 = memPool.allocate();
printf("p2:malloc: %p\n", p2);
memPool.deallocate(p);
printf("p:free: %p\n", p);
void* p3 = memPool.allocate();
printf("p3:malloc: %p\n", p3);
memPool.deallocate(p2);
printf("p2:free: %p\n", p2);
void* p4 = memPool.allocate();
printf("p4:malloc: %p\n", p4);
memPool.deallocate(p3);
memPool.deallocate(p4);
return 0;
}
//运行结果
调用MemPool构造函数
p:malloc: 0x121e05de0
p2:malloc: 0x121e05df0
p:free: 0x121e05de0
p3:malloc: 0x0
p2:free: 0x121e05df0
p4:malloc: 0x0
调用mempool析构函数~()