C/C++内存池

C/C++ 内存池

1、内存碎片

  1. 频繁的malloc和free会产生内存碎片
  2. 内存碎片通常分为内部碎片和外部碎片:
    1. 内部碎片是由于采用固定大小的内存分区,当一个进程不能完全使用分给它的固定内存区域时就产生了内部碎片,通常内部碎片难以完全避免;
    2. 外部碎片是由于某些未分配的连续内存区域太小,以至于不能满足任意进程的内存分配请求,从而不能被进程利用的内存区域。
  3. 什么是内存碎片具体参考这里

2、内存池设计

基于单项链表形式的内存池

  1. 申请一块较大的内存,其余所有需要用到的内存,都从这块大内存中申请和释放
  2. 提供4个接口,分别为: 创建内存池、销毁内存池、从内存池中申请内存、销毁从内存池中申请的内存;

2.1、内存池结构体设计

struct node { struct node* next; };

struct cache_allocer
{
    unsigned char* cache_member;
    int capacity;
    struct node* free_list;
    int elem_size;
};
  • cache_member 字段用于记录申请的内存首地址;
  • capacity 内存的容量,能存放多少个元素elem;
  • free_list 链表头用于存放未使用的内存空间首;
  • elem_size 每个元素的大小;

2.2、创建内存池的create_cache_allocer接口设计

struct cache_allocer* create_cache_allocer(int capacity, int elem_size)
{
    elem_size = (elem_size < sizeof(struct node)) ? sizeof(struct node) : elem_size;

    struct cache_allocer* allocer;
    allocer = (struct cache_allocer*)malloc(sizeof(struct cache_allocer));
    memset(allocer, 0, sizeof(struct cache_allocer));

    allocer->capacity = capacity;
    allocer->elem_size = elem_size;
    allocer->cache_member = (unsigned char*)malloc(capacity * elem_size);
    memset(allocer->cache_member, 0, sizeof(unsigned char*));
    allocer->free_list = NULL; 

    int i;
    struct node* walk;
    for (i = 0; i < capacity; i++)
    {   
        walk = (struct node*)(allocer->cache_member + i*elem_size);
        walk->next = allocer->free_list;
        allocer->free_list = walk; // 基于单项链表的形式最终指向于最后一块内存地址
        walk = NULL;
    }

    return allocer;
}
  • 参数capacity为总共可存多少个元素;参数elem_size为每个元素的字节数;
  • 第1行参数检验,保证每个元素的字节数最少是链表头指针的4个字节;
  • 第11行为内存池分配内存(内存存放个数为总容量*每个单位元素的字节);
  • 第15~23行主要为free_list赋值
    • 第一次执行 34行取出cache_member中第一段内存,转为链表的的形式,35行将null赋值给第一个的next指针,36行执行完,此时free_list指向的是第一段内存起始地址
    • 第二次执行 34行取出cache_member中第二段内存…35行将上一个循环取得内存付给第二次(当前取出的)的next指针,此时next指针指向自己前一段内存,36行将第二次取出的第二段内存地址付给链表头;
    • … 循环执行完成,最终的free_list链表头指向cache_member中最后一段内存, free_list中next指向它前一段地址(最终将cache_member中的内存用链表free_list倒序的关联管理起来)

2.3、释放内存池的destroy_cache_allocer接口设计

void destroy_cache_allocer(struct cache_allocer* allocer)
{
    if (allocer->cache_member != NULL)
    {
        free(allocer->cache_member);
    }
    free(allocer);
}

2.4、从内存池中申请内存的cache_alloc接口设计

void* cache_alloc(struct cache_allocer* allocer, int elem_size)
{
    if (allocer->elem_size < elem_size)
    {
        return NULL;
    }

    if (allocer->free_list != NULL)
    {
        void* now = allocer->free_list;
        allocer->free_list = allocer->free_list->next;
        return now;
    }

    return malloc(elem_size);
}
  • 参数allocer初始化好的内存池; 参数elem_size单个元素字节;
  • 第3行参数校验, 避免从内存池申请的空间单个元素比初始化时的内存池单个元素字节小 ;
  • 第9行从链表头free_list空闲的内存池中分配一段内存出来,将内存地址赋给now;
  • 第10行将空闲内存池的链表头,向后挪动一个存储单元(获取当前链表头free_list的next指向的内存赋值给当前的链表头free_list<更新链表头>);
  • 第11行返回这段空间的地址;

2.5、释放从内存池中申请的内存的cache_free接口设计

void cache_free(struct cache_allocer* allocer, void* member)
{
    if (allocer == NULL || member == NULL)return;

    if (((unsigned char*)member) >= allocer->cache_member && ((unsigned char*)member) < allocer->cache_member + allocer->capacity*allocer->elem_size)
    {
        struct node* node = member;
        node->next = allocer->free_list;
        allocer->free_list = node;
        return;
    }

    free(member);
}
  • 参数allocer初始化好的内存池; 参数member从内存池中分配出来的空间;
  • 第5行校验member是否是从内存池中分配出来的。
  • 第7~9行主要就是把member放到free_list链表头,member中的next指向原来的free_list链表头。从而达到插入一段空间。

2.6、使用

int main(int argc, char** argv) 
{
    printf("star\n");

    struct Msg
    {
        int type;
        int line;
    };

    struct cache_allocer* allocer;
    allocer = create_cache_allocer(100, sizeof(struct Msg));

    for (int i = 1; i <= 100; i++)
    {
        //struct Msg* msg = (struct Msg*)malloc(sizeof(struct Msg));
        //msg->type = 21;
        //msg->line = 4;
        //free(msg);
        struct Msg* msg = cache_alloc(allocer, sizeof(struct Msg));
        msg->type = 21;
        msg->line = 4;
        cache_free(allocer, msg);
    }
    destroy_cache_allocer(allocer);

    printf("end\n");
    return 0;
}
  • 第12行创建一个内存池装可以100个元素(Msg结构体),大小100*8=800;
  • 第20行从内存池中为分配一块8个字节的内存给msg;
  • 第23行释放从内存池分配出来的空间;(其实释放着8个字节只是将他放到了空闲链表中,数据还在,只是在下载次分配时, 直接新数据覆盖了这段空间)
  • 第25行释放内存池;

猜你喜欢

转载自blog.csdn.net/sszyc8/article/details/81192539