内存池的实现(一)

引言

C/C++下内存管理是让几乎每一个程序员头疼的问题,分配足够的内存、追踪内存的分配、在不需要的时候释放内存——这个任务相当复杂。而直接使用系统调用malloc/free、new/delete进行内存分配和释放,有以下弊端:

  1. 调用malloc/new,系统需要根据“最先匹配”、“最优匹配”或其他算法在内存空闲块表中查找一块空闲内存,调用free/delete,系统可能需要合并空闲内存块,这些会产生额外开销
  2. 频繁使用时会产生大量内存碎片,从而降低程序运行效率
  3. 容易造成内存泄漏

 

内存池(memory pool)是代替直接调用malloc/freenew/delete进行内存管理的常用方法,当我们申请内存空间时,首先到我们的内存池中查找合适的内存块,而不是直接向操作系统申请,优势在于:

  1. 比malloc/free进行内存申请/释放的方式快
  2. 不会产生或很少产生堆碎片
  3. 可避免内存泄漏

 

内存池设计

看到内存池好处这么多,是不是恨不能马上抛弃malloc/free,投奔内存池的怀抱呢?且慢,在我们自己动手实现内存池之前还需要明确以下几个问题:

  1. 内存池的空间如何获得?是程序启动时分配一大块空间还是程序运行中按需求分配?
  2. 内存池对到来的内存申请,有没有大小的限制?如果有,最小可申请的内存块为多大,最大的呢?
  3. 如何合理设计内存块结构,方便我们进行内存的申请、追踪和释放呢?
  4. 内存池占用越多空间,相对应其他程序能使用的内存就越少,是否要设定内存池空间的上限?设定为多少合适呢?

 

带着以上问题,我们来看以下一种内存池设计方案。

 

内存池实现方案一

这里下载该内存池实现的源码。

首先给出该方案的整体架构,如下:

内存池实现架构

图1.内存池架构图

结构中主要包含blocklist 和pool这三个结构体,block结构包含指向实际内存空间的指针,前向和后向指针让block能够组成双向链表;list结构中free指针指向空闲 内存块组成的链表,used指针指向程序使用中的内存块组成的链表,size值为内存块的大小,list之间组成单向链表;pool结构记录list链表的头和尾。

 

内存跟踪策略

该方案中,在进行内存分配时,将多申请12个字节,即实际申请的内存大小为所需内存大小+12。在多申请的12个字节中,分别存放对应的list指针(4字节)、used指针(4字节)和校验码(4字节)。通过这样设定,我们很容易得到该块内存所在的list和block,校验码起到粗略检查是否出错的作用。该结构图示如下:

结点头12字节说明

图2.内存块申请示意图

图中箭头指示的位置为内存块真正开始的位置。

 

内存申请和释放策略

申请:根据所申请内存的大小,遍历list链表,查看是否存在相匹配的size;

    存在匹配size:查看free时候为NULL

      free为NULL:使用malloc/new申请内存,并将其置于used所指链表的尾部

      free不为NULL:将free所指链表的头结点移除,放置于used所指链表的尾部

    不存在匹配size:新建list,使用malloc/new申请内存,并将其置于该list的used所指链表尾部

   返回内存空间指针

释放:根据内存跟踪策略,获取list指针和used指针,将其从used指针所指的链表中删除,放置于free指针所指向的链表

 

对方案一的分析

对照“内存池设计”一节中提出的问题,我们的方案一有以下特点:

  1. 程序启动后内存池并没有内存块,到程序真正进行内存申请和释放的时候才接管内存块管理;
  2. 该内存池对到来的申请,对申请大小并不做限制,其为每个size值创建链表进行内存管理;
  3. 该方案没有提供限定内存池大小的功能

 

结合分析,可以得出该方案应用场景如下:程序所申请的内存块大小比较固定(比如只申请/释放1024bytes或2048bytes的内存),申请和释放的频率基本保持一致(因申请多而释放少会占用过多内存,最终导致系统崩溃)。

 

这篇文章讲解了内存管理的基本知识,以一个简单的内存池实现例子作为敲门砖,引领大家认识内存池,下一篇为内存池进阶文章,讲解apache服务器中内存池的实现方法。

 

Reference: 《内存池设计研究与应用》by freeeyes

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内存池是一种常用的内存分配方式,可以避免频繁的内存申请和释放,提高内存使用效率。下面是一个简单的C语言实现内存池的例子: ```c #include <stdio.h> #include <stdlib.h> #define BLOCK_SIZE 4096 // 每个内存块的大小 #define MIN_ALLOC 16 // 最小分配单位 #define MAX_ALLOC 1024 // 最大分配单位 // 内存块结构体 typedef struct _memory_block { struct _memory_block *next; // 下一个内存块 char *data; // 数据区域 size_t used; // 已使用的大小 size_t size; // 总大小 } memory_block; // 内存池结构体 typedef struct _memory_pool { memory_block *first; // 第一个内存块 memory_block *last; // 最后一个内存块 } memory_pool; // 初始化内存块 memory_block *memory_block_init(size_t size) { memory_block *block = (memory_block *)malloc(sizeof(memory_block)); block->next = NULL; block->data = (char *)malloc(size); block->used = 0; block->size = size; return block; } // 销毁内存块 void memory_block_destroy(memory_block *block) { free(block->data); free(block); } // 初始化内存池 void memory_pool_init(memory_pool *pool) { pool->first = NULL; pool->last = NULL; } // 销毁内存池 void memory_pool_destroy(memory_pool *pool) { memory_block *block = pool->first; while (block) { memory_block *temp = block; block = block->next; memory_block_destroy(temp); } } // 从内存池中分配内存 void *memory_pool_alloc(memory_pool *pool, size_t size) { // 对齐内存大小 size = (size + MIN_ALLOC - 1) / MIN_ALLOC * MIN_ALLOC; // 遍历内存块列表 memory_block *block = pool->first; while (block) { // 查找可用内存 if (block->size - block->used >= size) { void *data = block->data + block->used; block->used += size; return data; } block = block->next; } // 没有可用内存,创建新的内存块 size_t block_size = size > BLOCK_SIZE ? size : BLOCK_SIZE; memory_block *new_block = memory_block_init(block_size); if (!pool->first) { pool->first = new_block; } else { pool->last->next = new_block; } pool->last = new_block; // 分配内存 void *data = new_block->data; new_block->used = size; return data; } // 从内存池中释放内存(不做实际操作) void memory_pool_free(memory_pool *pool, void *data) { // 不做实际
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值