固定大小内存的内存池实现
该内存池是一个很简单的内存池,只能申请固定大小的内存,仅供学习
要点:
- 构造隐式链表
- 二级指针
存储结构
typedef struct mempool_s{ int block_size; // 每一块的大虚哎 int free_count; // 剩余有多少块是可以用的 char *free_ptr; // 可以分配的开始位置 char *mem; // 指向4k的起始位置 }mempool_t;
开放的API
int memp_init(mempool_t * m, int block_size)
对内存池每一项都赋值,用malloc
申请大块内存,让mem
指向malloc
,
这里面有特殊操作:构建隐式链表void* memp_alloc(mempool_t* m)
以free_ptr
链开始分配void* memp_free(mempool_t* m, void *ptr)
释放给定的内存块,并将它头插在隐式链表中
构建隐式链表
我们会用malloc
分配一个4k
大小的内存,mem
指向这块起始地址,free_ptr
指向可以分配的内存的起始地址
每次从内存池中分配一个32
字节大小的内存块,于是4k
内存就分分割为下图所示
现在就有一些问题
- 如何管理些块块内存?
- 分配时分配哪一个块?
- 释放了的块怎么回收?
上述这些问题全部都可以用隐式链表来解决
构造隐式链表的思路:
每次我们申请的是32
个字节,我们可以将前面8
个字节拿来存储next
指针,也就是下一个块的地址,这样我们的内存就变成了这样
这样的话我们就构建了一个隐式链表,每次分配就从头部取,每次挥手就将32B
的块头插进这个链表
如何实现
利用二级指针可以很方便的实现隐式链表的构造。
二级指针是一个指向指针的指针
操作
假设有下述4K
内存
char * free_ptr = malloc(1024);
每个块的大小为32
用变量block_size
表示
int block_size = 32;
一共有1024/32
个块用free_count
表示
int free_count = 1024 / block_size;
使用二级指针
int i = 0; char *ptr = m->free_ptr; for(i = 0; i < m->free_count; i++){ *(char**)ptr = ptr + block_size; ptr += block_size; } *(char**)ptr = NULL;
ptr
一级指针被强转为了二级指针,ptr
本来是指向一个字节(char
)的指针,现在变为了指向8
个字节的指针,
对于这一*(char**)ptr
解引用操作,就能够操作以ptr
原本指向的一字节开始的八个字节,就将这8
个字节赋予为下一个块的地址ptr + block_size
,这里不懂的话最后还会有一个二级指针操作队列尾部的例子。
这里我有必要解释一下一级指针与二级指针以
char
为例
char*
一级指针,指向char
类型的指针,char
类型为一字节,也可以说是指向一个字节的指针char**
二级指针,指向char*
类型的指针,由于64
位下指针类型大小为8
个字节所以,也可以说是指向8
个字节的指针
经过上述操作,就隐式的构建了链表这样一来内存池内存块的管理就实现了。
- 分配时从
free_ptr
中取出 - 回收时从将内存块插入链表的头部
完整代码实现
#include <stdio.h> #include <stdlib.h> // 实现一个分配固定大小块的内存池 // 将4k的内存分割为大小相等的块 比如 32bytes/block 就有128个块 #define MEM_PAGE_SIZE 0X1000 typedef struct mempool_s{ int block_size; // 每一块 int free_count; // 剩余有多少块 可以用 char *free_ptr; // 可以分配的开始位置 char *mem; // 指向4k的起始位置 }mempool_t;
初始化内存池块
int memp_init(mempool_t * m, int block_size){ // 初始化4k内存 if(!m) return -2; // 对内存池的每一项赋值 m->block_size = block_size; m->free_count = MEM_PAGE_SIZE / block_size; // 4k/32 m->free_ptr = (char*)malloc(MEM_PAGE_SIZE); if(!m->free_ptr) return -1; m->mem = m->free_ptr; // 构建了隐式链表 int i = 0; char *ptr = m->free_ptr; for(i = 0; i < m->free_count; i++){ *(char**)ptr = ptr + block_size; ptr += block_size; } *(char**)ptr = NULL; return 0; }
分配内存块
// 以free_ptr链开始分配 void* memp_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* memp_free(mempool_t* m, void *ptr){ // // 将空闲块头插 进空闲列表 *(char**)ptr = m->free_ptr; void * old_free = (void*)m->free_ptr; m->free_ptr = (char*)ptr; m->free_count++; return old_free; }
测试
int main(){ mempool_t m; int i = memp_init(&m, 32); printf("%d\n", i); void* p1 = memp_alloc(&m); printf("memp_alloc : %p\n", p1); void* p2 = memp_alloc(&m); printf("memp_alloc : %p\n", p2); void* p3 = memp_alloc(&m); printf("memp_alloc : %p\n", p3); void* p4 = memp_alloc(&m); printf("memp_alloc : %p\n", p4); memp_free(&m, p1); memp_free(&m, p3); void* p5 = memp_alloc(&m); printf("memp_alloc : %p\n", p5); void* p6 = memp_alloc(&m); printf("memp_alloc : %p\n", p6); }
打印
memp_alloc : 0x5624aa9cc260 # p1 memp_alloc : 0x5624aa9cc280 # p2 memp_alloc : 0x5624aa9cc2a0 # p3 memp_alloc : 0x5624aa9cc2c0 # p4 memp_alloc : 0x5624aa9cc2a0 # p5 memp_alloc : 0x5624aa9cc260 # p6
这个内存池存在的问题
可以明显的看见,我们分配的32个字节的内存其中将前面8个字节用来构造了隐式链表,所以实际能用的内存只有32-8
,并且如果你分配的块的大小小于8
,则上述程序会出现段错误
二级指针操作自定义类型
自定义类型二级指针的应用
假设有下列结构
typedef struct task_s { task_t *next; // 必须是第一个字段 handler_pt func; void *arg; } task_t;
初始化
task_t *a = malloc(sizeof(task_t));
二级指针操作
*(task_t**)a = NULL; // 等价于 a->next = NULL;
解释:a
本来是一个task_t
类型的指针,由于task_t
有24
个字节,也可以说a
是指向24
个字节的指针,由于a
被转为了二级指针,意思就是现在a
是一个指向8
个字节的指针,该8
个字节就对应task_t
中前8
个字节也就是next
指针,与是解引用就是相当于解了next
指针的引用