写内存池,是为了锻炼链表的使用能力,以及学一点工程实用的东西。内存池分为单线程和多线程的,固定分配长度和可变分配长度的;这里,我看了两套代码,均是单线程的,一套讲分配固定长度的,一套将分配可变长度的。
代码一:Github上面找到的一套开源代码:实现分配可变长度的内存
代码链接如下:https://github.com/userpro/MemoryPool
作者的构思是:
1.数据的组织结构:
{
unsigned int last_id;
mem_size_t mem_pool_size, total_mem_pool_size;
struct _mem_pool_list *mlist;
bool auto_extend;
}MemoryPool;
{
char *start;
unsigned int id;
mem_size_t alloc_mem;
mem_size_t alloc_prog_mem;
Chunk *free_list, *alloc_list;
struct _mem_pool_list *next;
}Memory;
{
mem_size_t alloc_mem;
struct _chunk *prev;
struct _chunk *next;
bool is_free;
}Chunk
MemoryPool是Memory的信息管理;Memory是向系统申请的内存块;Chunk是向内存池申请任意长度的内存时需要加进去的内存管理信息(头部加chunk,尾部加*chunk)
2.该内存池对外提供的API:
内存池操作部分:
MemoryPool *MemoryPool_Init(mem_size_t mempoolsize, bool auto_extend);
void *MemoryPool_Alloc(MemoryPool *mp, mem_size_t wantsize);
bool MemoryPool_Free(MemoryPool *mp, void **p);
MemoryPool *MemoryPool_Clear(MemoryPool *mp);
bool MemoryPool_Destroy(MemoryPool *mp);
获取内存池信息部分:
void get_memory_list_count(MemoryPool *mp, mem_size_t *mlist_len);
void get_memory_info(Memory *mm, mem_size_t *free_list_len, mem_size_t *alloc_list_len);
int get_memory_id(Memory *mm);
double get_mempool_usage(MemoryPool *mp);
double get_mempool_prog_usage(MemoryPool *mp);
其中,
MemoryPool_Init是初始化内存池为一个Memory块,并初始化三个结构体中的内存池信息;
MemoryPool_Alloc是向内存池申请一个大小为sizeof(chunk)+wantsize+sizeof(*chunk)的内存,并将该块内存从Memory结构体中的free_list上摘下来,挂到alloc_list上去;chunk头中有关于该内存的大小,以及下一个alloc_list上的chunk地址;*chunk指向该chunk头,个人理解是作为该内存结束的一个标识;若要申请的内存小于当前的空闲内存大小-chunk头和*chunk尾的话,则在该Memory块上进行分配,否则,将该Memory块上的空闲内存挂到alloc_list上去,如果MemoryPool的auto_extend为1,则自动向系统重新申请一个Memory,然后进行内存池的分配;
MemoryPool_Free是将从内存池申请的内存,归还给内存池;该函数里需要完成两个部分:一是将该内存从alloc_list上摘下,挂回到free_list上去,二是需要将归还的内存和free_list上已有的空闲内存进行合并,使之成为只有一个chunk头和一个*chunk尾的内存空间,便于下一次任意长度内存的申请;
MemoryPool_Clear是将内存池格式化到初始化的状态,即没有分配内存出去;
MemoryPool_Destroy是将内存池中的内存块全部free给系统;
get_memory_list_count是获取当前内存池中Memory块的总个数;
get_memory_info是获取当前Memory块中free_list和alloc_list的长度;
get_memory_id是获取当前的Memory块的编号;
get_mempool_usage是获取当前内存池实际分配的空间占内存池总空间的百分比;
double get_mempool_prog_usage是获取当前内存池真正分配给程序(除去chunk头和*chunk尾的部分)用来存数据的内存占内存池总空间的百分比;
3.期间遇到的问题及解决方案:
MemoryPool_Free在运行时,有一处发生了指针越界的问题,因为该作者注释少,且中间变量名随意,对函数想实现的功能也没有说明,导致我看了好久还是不明白作者写的是什么。后来我屏蔽掉了该部分的代码,根据运行结果以及推测,猜测处作者的意图,然后修改了该部分的代码,修改的极为简单,以至于我还是觉得自己没有明白作者真正的意图,不过这不重要,重要的是问题解决了,也没有出现BUG;但是free掉返回到main函数后,我发现被MemoryPool_Free掉的内存地址仍然可以访问,为了解决这个问题,我使用了指针的指针,将该内存地址的地址作为**p传进了MemoryPool_Free函数,并在MemoryPool_Free尾部将该**p的内容置为了NULL,即将该内存地址置为了NULL;
作者的源代码如下:
static bool merge_free_chunk(MemoryPool *mp, Memory *mm, Chunk *c)
{
if (!mp || !mm || !c)
return 0;
Chunk *p0 = *(Chunk **)((char *)c - CHUNKEND), *p1 = c;
while ((char *)p0 > mm->start && p0->is_free)
{
p1 = p0;
p0 = *(Chunk **)((char *)p0 - CHUNKEND);
}
p0 = (Chunk *)((char *)p1 + p1->alloc_mem);
while ((char *)p0 < mm->start + mp->mem_pool_size && p0->is_free)
{
dlinklist_delete(mm->free_list, p0);
p1->alloc_mem += p0->alloc_mem;
p0 = (Chunk *)((char *)p0 + p0->alloc_mem);
}
*(Chunk **)((char *)p1 + p1->alloc_mem - CHUNKEND) = p1;
return 1;
}
我修改后的如下:
static bool merge_free_chunk(MemoryPool *mp, Memory *mm, Chunk *c)
{
if (!mp || !mm || !c)
return 0;
Chunk *p0 = (Chunk *)((char *)c + c->alloc_mem ), *p1 = c;
if ((char *)p0 < mm->start + mp->mem_pool_size && p0->is_free)
{
p1->next = NULL;
p0->prev = NULL;
p1->alloc_mem += p0->alloc_mem;
p0 = *(Chunk **)((char *)p0 + p0->alloc_mem - CHUNKEND);
p0 = p1;
*(Chunk **)((char *)p1 + p1->alloc_mem - CHUNKEND)= NULL;
}
return 1;
}
4.感悟
指针类型强制转化后,其实改变的是该指针在寻址时候的对齐方式,对内存本身并无影响;所以对一块内存地址,可以用类型不同的指针进行访问;
代码风格很重要,起合适的变量名、函数名、写好注释十分重要。
代码二:优快云上找到的一篇注释很好的代码:实现固定长度的内存分配
代码链接如下:https://blog.youkuaiyun.com/oguro/article/details/52705625
作者的构思是:
1.数据的组织结构:
{
int cnt; //数量
int usedcnt; //使用个数
int blocksize; //内存块大小
char* firstaddr; //起始地址
char* lastaddr; //结束地址
MEMBLOCK *firstblock; //指向下一节点的指针
}mempool
{
char* pmem; //内存指针
MEMBLOCK *next; //指向下一节点的指针
}memblock;
mempool是memblock的信息管理;memblock是向系统申请的内存块;
2.该内存池对外提供的API:
/********************************************
*@brief 创建n个节点的内存池链表
*@param
* num 数量
* blocksize 内存块大小
*@return OPERA_OK/OPERA_ERR
*@see
*********************************************/
mempool *CreatePool(int num, int blocksize);
/********************************************
*@brief 销毁内存池
*@param
* poolhead 内存池指针
*@return OPERA_OK/OPERA_ERR
*@see
*********************************************/
int DestroyPool(mempool *poolhead);
/********************************************
*@brief 得到一个内存块
*@param
* poolhead 内存池指针
*@return 内存块地址
*@see
*********************************************/
memblock *GetMemblock(mempool *poolhead);
/********************************************
*@brief 释放一个内存块
*@param
* pmem 内存块地址
* poolhead 内存池指针
*@return OPERA_OK/OPERA_ERR
*@see
*********************************************/
int ReleaseMemblock(memblock* pmem, mempool *poolhead);
3.期间遇到的问题及解决方案:
没有遇到什么问题,但该作者写的代码比较啰嗦,我精简了一部分;
还有,该作者向内存池申请内存时,没有判断内存池的大小;
4.感悟
该作者的注释风格很好,值得学习。