C语言——内存池代码的阅读

写内存池,是为了锻炼链表的使用能力,以及学一点工程实用的东西。内存池分为单线程和多线程的,固定分配长度和可变分配长度的;这里,我看了两套代码,均是单线程的,一套讲分配固定长度的,一套将分配可变长度的。

 

代码一: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.感悟

指针类型强制转化后,其实改变的是该指针在寻址时候的对齐方式,对内存本身并无影响;所以对一块内存地址,可以用类型不同的指针进行访问;

代码风格很重要,起合适的变量名、函数名、写好注释十分重要。

 

代码二:CSDN上找到的一篇注释很好的代码:实现固定长度的内存分配

代码链接如下:https://blog.csdn.net/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.感悟

该作者的注释风格很好,值得学习。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值