C语言实现内存池

什么是内存池,这里简单介绍一下(不做详细说明),内存池技术是一种用于分配大量大小相同的小对象的技术,通过该技术可以极大加快内存分配/释放过程。其原理是先申请一大块内存,然后分成若干个大小相等的小块,用链表的方式将这些小块链在一起,当开发人员需要使用内存时(分配),从链表头取下一块返回给开发人员使用;当开发人员使用完毕后(释放),再将这块内存重新挂回链表尾。

这样操作的好处有如下三点:
1、提高分配和释放的效率;
2、避免开发人员忘记释放内存造成内存泄露;
3、减少内存占用(频繁的malloc是很占内存空间的,至于为什么我就不说了,可以到网上搜索内存管理相关的内容);

关于内存池技术的代码在网上也有不少,我也看过一些,有基于C现实的,也有C++实现的,但在这些文章里都发现两个问题,1、当开发人员使用完内存后,如果不释放,那么还是会有内存泄露的情况;2、在开发人员使用完内存后,释放的时候总会去循环判断该地址是否存在,在这种情况下,释放的效率并有没有得到提升。

那么如何解决这两个问题呢,我先简单介绍一下我这个内存池,先看段结构体:


typedef struct _memory_pool  
{  
	unsigned int blockCount;      //申请块数量  
	unsigned int blockCountStep;  //内存块数增长步长  
	unsigned int blockSize;       //单个块的大小  
	unsigned int freeCount;       //空闲的内存块数  
	MemoryBlock *freeHead;        //空闲的头  
	MemoryBlock *freeTail;        //空闲的尾  
	MemoryBlock *usedHead;        //已使用的头  
	MemoryBlock *usedTail;        //已使用的尾  
	char *pBlockMemoryHead;       //块的头指针,用于释放内存时候用(因为块的分配了一大块的内存)  
	char *pDataMemoryHead;        //数据区域头指针  
} MemoryPool;  

这里面描述了详细存放信息,当池初始化时,所有块都存在空闲块中,已使用块为空,当申请一块空间时,从空闲块的头取下来放到已使用的块中,基本过程就是这样子的。
下面来解决上面提到的两个问题,
1、在内存泄露方面,先申请两大块的连续空间,一块用于存放块的基本信息(块链表),另一块返回给开发人员使用(数据区域),然后用指针保存两块内存地址,这样一来,不管你是否释放,在程序需要清理内存的时候,我只需要free两次;
2、在释放效率方面,我们不使用循环方式来处理,而是在返回给开发人员使用的内存块前面加4个字节存放与他关联的块信息块地址,在释放的时候,我们只需要 (MemoryBlock*)((char *)ptr – 4) 就能得到块地址,这样就很方便的挂回空闲块。
具体现实代码如下:


#include <stdio.h>  
#include <malloc.h>  
#include <string.h>  
   
typedef struct _memory_block  
{  
    struct _memory_block *pNext; //下一个内存单元  
    struct _memory_block *pPrev; //上一个内存单元  
    char* data; //数据,  
} MemoryBlock;  
   
typedef struct _memory_pool  
{  
    unsigned int blockCount;     //申请块数量  
    unsigned int blockCountStep; //内存块数增长步长  
    unsigned int blockSize;      //单个块的大小  
    unsigned int freeCount;      //空闲的内存块数  
    MemoryBlock *freeHead;       //空闲的头  
    MemoryBlock *freeTail;       //空闲的尾  
    MemoryBlock *usedHead;       //已使用的头  
    MemoryBlock *usedTail;       //已使用的尾  
    char *pBlockMemoryHead;      //块的头指针,用于释放内存时候用(因为块的分配了一大块的内存)  
    char *pDataMemoryHead;       //数据区域头指针  
} MemoryPool;  
   
int Xpool_init(unsigned int blockCount, unsigned int blockSize);  
int Xpool_destroy(void);  
void* Xpool_alloc(unsigned int size);  
int Xpool_free(void *ptr);  
   
static int Xpool_block(unsigned int blockCount, unsigned int blockSize);  
static MemoryPool memory;  
   
//初始化内存池  
int Xpool_init(unsigned int blockCount, unsigned int blockSize)  
{
    MemoryPool *p = &memory;  
    p->blockCount = blockCount;  
    p->blockSize = blockSize;  
    p->freeCount = blockCount;  
    p->blockCountStep = 100;  
    p->freeHead = p->freeTail = NULL;  
    p->usedHead = p->usedTail = NULL;  
       
    Xpool_block(blockCount, blockSize);  
    return 0;  
}  
   
//申请块,并且把新申请的块连到空闲块后面  
static int Xpool_block(unsigned int blockCount, unsigned int blockSize)  
{
    MemoryPool *p      = &memory;  
    MemoryBlock *pFree = NULL;//空闲块连表指针

    //分配一大块连续的内存块空间存放块信息 
    p->pBlockMemoryHead = (char *)malloc(sizeof(MemoryBlock) * blockCount);
    //分配一大块连续的内存空间存放供用户使用的空间
    p->pDataMemoryHead  = (char *)malloc((blockSize + sizeof(MemoryBlock *)) * blockCount);
       
    for(unsigned int i = 0; i < blockCount; i++)  
    {
        // (MemoryBlock *)malloc(sizeof(MemoryBlock));
        pFree = (MemoryBlock *)(p->pBlockMemoryHead + (sizeof(MemoryBlock) * i));
        // (void *)malloc(blockSize + sizeof(MemoryBlock *)); // 分配一块数据区域
        pFree->data = p->pDataMemoryHead + ((blockSize + sizeof(MemoryBlock *)) * i);
        pFree->pNext = NULL;
        pFree->pPrev = p->freeTail;
           
        if(p->freeHead == NULL){
            p->freeHead = p->freeTail = pFree;
        } else {
            p->freeTail->pNext = pFree;
            p->freeTail = pFree;
        }
    }

    return 0;  
}  
   
//销毁内存池  
int Xpool_destroy(void)
{
    MemoryPool *p = &memory;
    //释放内存块所占的内存
    free(p->pBlockMemoryHead);
    //释放数据区域所占的内存
    free(p->pDataMemoryHead);
    return 0;
}  
   
//申请内存  
void* Xpool_alloc(unsigned int size)
{  
    MemoryPool *p      = &memory;  
    MemoryBlock *block = NULL;  

    if(p->freeHead == NULL){
        // 没有可用空间  
        Xpool_block(p->blockCountStep, p->blockSize);
    }  
       
    block = p->freeHead;//获取表头内存块  
    p->freeHead = block->pNext;//将空闲块的链表头  
    p->freeCount--;//空闲块数量减一  
    block->pNext = NULL;  
    block->pPrev = p->usedTail;//这个块的上个块是已使用块的最后一个块  
       
    // 第一次使用内存?  
    if(p->usedHead == NULL){  
        p->usedHead = p->usedTail = block;//,则已使用的头和尾都指向这个块  
    }else{//不是第一次  
        p->usedTail->pNext = block;  
        p->usedTail = block;  
    }
    //留下data里一个指针的空间,用于保存与数据关联的块地址  
       
    block->data = (char *)block;  
    return (char *)block->data + sizeof(MemoryBlock *);  
}
   
//回收内存  
int Xpool_free(void *ptr)  
{  
    MemoryPool *p = &memory;  
    char *realptr = (char *)ptr - sizeof(MemoryBlock *); //数据块真实的起始地址  
    MemoryBlock *block = (MemoryBlock *)realptr;  
       
    if(block == NULL){  
        return NULL;  
    }  
       
    if(block->pPrev == NULL){
        //如果是头  
        p->usedHead = block->pNext;  
        if(p->usedHead != NULL){
            p->usedHead->pPrev = NULL;  
        }  
    } else if(block->pNext == NULL){
        //如果是尾  
        p->usedTail = block->pPrev;  
        if(p->usedTail != NULL){  
            p->usedTail->pNext = NULL;  
        }  
    } else {
        //中间的  
        block->pPrev->pNext = block->pNext;  
        block->pNext->pPrev = block->pPrev;  
    }  
       
    //重置参数  
    block->pPrev = p->freeTail;  
    block->pNext = NULL;  
    block->data = realptr;  
       
    //加到空闲块链表  
    p->freeTail->pNext = block;  
    p->freeTail = block;  
    p->freeCount++;  
       
    return 0;  
}
   
int main(int argc, char *argv[])  
{  
    Xpool_init(10, 96);  
    char *p = (char *)Xpool_alloc(20);  
    Xpool_free(p);  
    Xpool_destroy();  
    return 0;  
}  

 

Leave a Reply

Your email address will not be published.