原文放在另一个博客地址:
为什么要内存池?
(1) 在调试阶段检查内存泄露。C++的指针给大家带来了很大的方便,但是它对内存的自由操作也带来了内存泄露等一系列问题,在一些嵌入式软件开发过程中
(2) 减少内存碎片。频繁的从堆中new一些小内存容易造成内存碎片增加。
(3) 针对特定应用编写特定功能的内存池,可以提高运行效率。New和delete都是从堆中申请和释放内存,如果程序中频繁的从堆中申请和释放内存,会造成系统效率的下降。
内存池设计的思想:
程序的内存使用应该“我的地盘我做主”,自己管理内存分配和回收而不再频繁的求助与系统的堆分配。
内存池的主要设计思想是:先从堆中申请一大块“内存块”,后续的new操作就从这个大“内存块”里取,用完之后再返回该“内存块”,只有在申请的大内存块用完后才向堆中进行申请,这个大“内存块”就像池子一样代替堆向我们提供内存,称之为“内存池”
内存池的分类:
内存池按照所提供内存单元大小是否可变分为:可变大小内存池与固定大小内存池。
可变大小内存池是指根据申请的大小而进行分配,实行“要多少就给多少”的内存分配方法。它的优点是:充分利用内存空间,不会造成太多的资源浪费;它的缺点是:效率低、内存池管理(包括分配和回收)困难。
固定大小内存池,是指申请的内存单元是固定大小,例如,当前内存池中提供大小为8 Byte、16 Byte和24 Byte这三种大小的内存单元,如果需要申请一个20Byte,则内存池会给它一个24 Byte的内存,而不是申请的20。(<----看看,这里存在了内存的浪费)。它的优点是:效率高,便于管理和分配。它的缺点是:容易造成内存空间的浪费。
重载new与delete
在面向对象的编程过程中,主要使用new从堆中申请内存并生成相应的对象。如果用户要自己管理程序的内存使用就需要将new和内存池结合起来一起,因此,需要重载new,当然重载new的过程也必然需要重载对应的delete。
我们经常说的New其实有三种形式:new运算符、operate new和placement new;例如:int*pi = new int中的new就是new运算符 ,new运算符包括两部分:operate new和placement new。其中operate new:用于申请内存;placement new:用于在已申请的内存上调用构造函数构造对象。显然,如果要使用内存池管理内存则需要重载operate new。
STL的分级内存分配体制
内存池在很多地方都有使用,例如STL中所采用的多级内存分配方式就包括内存池的管理,STL的多级内存分配方式的思想是这样的:
(1)如果所申请的内存单元大于128Byte,则这些内存申请全部从堆中直接分配。
(2)如果所申请的内存单元小于或等于128Byte,则这些内存申请主要从STL的内存池中进行分配。
STL的内存池采用固定大小的方式,提供了8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128共16种固定大小的内存分配,例如向STL中申请100Byte的内存,那么STL的内存池实际提供给你的是104Byte而不是100Byte。
一种内存池的实现方法
下面提供一种内存池的实现方法,附件中包括源代码,在该内存池的实现中主要包括两部分:内存单元的管理与维护,内存块链表的管理与维护。
内存单元管理部分一般只管理没有分配出去的内存单元(当然,如果有特殊需要,也可以自己定义一个数据结构来管理已经分配出去的内存单元,在内存池里是你的天下,你想怎么管理就可以怎么管理)。内存单元的管理常用的方法有链表式或标志位式,详细信息请参考文档《内存单元管理.pptx》。
链表式内存单元管理主要是将内存块中的内存单元使用数组链表的方式串联起来,在每个内存单元的首4个字节用于存放下一个可用内存单元的地址。内存块块头一般记录管理本内存块中内存单元的分配信息,例如 (1)本内存块中的总内存单元数目 ,(2)已分配的内存单元数 ,(3)下一个可用的内存单元地址或索引。分配内存单元时,从内存单元链表取出一个内存单元即可,同时将下一个可用内存单元的地址放入相应的管理结构体pNextFreeItem 中,如下图所示:
回收内存单元时,只需要将新内存单元插入内存单元链表的首部即可。例如在下图(1)中回收单元i,首先将pNextFreeItem中的地址放入新回收内存单元的首4个字节中,如下图(2)所示,然后将新回收内存单元的地址放入pNextFreeItem中,如下图(3)所示:
(1)
(2)
(3)
标志位式内存单元管理主要使用一个bit表示一个内存单元的使用状态,例如1表示已分配,0表示未分配,如下图所示:
其当前的内存单元分配情况为:
1010010110101 ; 分配内存单元时:如果要分配第 i 块内存单元,则只需要将第 i 块的状态标志位修改为 1 ,同时将空闲内存单元数 -1 即可。 如左图,回收后状态标志变为 1010010110101 ;回收内存单元时:如果回收第 i 块内存单元,则只需要将第 i 块的状态标志位修改为 0 ,同时将空闲内存单元数 +1 即可,回收后状态标志变为 1010010100101 。本人写了一个内存池的小程序,需要的兄弟们可以联系我,在该例子程序中就是采用标志位式内存单元管理方式,其数据结构的声明如下:
struct _tMemBlock
{
void *m_pDataFirst;//数据区的起始地址
_tMemBlock *m_pNextMemBlock;//下一个内存块地址;
size_t m_iItemSize;//内存单元大小
size_t m_iMemBlockSize;//内存块大小
int m_iTotalItemNum;//本内存块内总的内存单元数
int m_iFreeItemNum;//本内存块中剩余的内存单元数
bitset<DEFAULT_MAX_ITEM_NUM> m_bsItemStatus;//第i位表示第i个内存单元是否为已分配状态,1为已分配状态,0为未分配状态;
bool _InitBlock(size_t iMemBlockSize = DEFAULT_MEMBLOCK_SIZE,size_t iMemItemSize = DEFAULT_MEMITEM_SIZE);
int _GetFreeItemIndex();//获取当前可用内存单元的索引
bool SetItemStatus(int iPos,int iStatus);//设置某内存单元的使用状态,1为使用,0为空闲
_tMemBlock(size_t iMemBlockSize = DEFAULT_MEMBLOCK_SIZE,size_t iMemItemSize = DEFAULT_MEMITEM_SIZE);
void* AllocItem(size_t iMemItemSize = DEFAULT_MEMITEM_SIZE);
bool FreeItem(void *pItem);
bool HasItem(void *pItem);
int GetFreeItemCounter();//获取本内存块中已经使用的内存单元数目
};
内存块链表的管理与维护,程序中该部分的数据结构声明如下:
class CMemPool
{
size_t m_iItemSize;//内存单元大小
size_t m_iMemBlockSize;//内存块大小
_tMemBlock* m_pFirstBlock;//起始内存块地址
bool m_bWiteLog;
fstream m_LogFile;
private:
bool CloseLogFile();
bool _InitMemPool(size_t iMemBlockSize = DEFAULT_MEMBLOCK_SIZE,size_t iMemItemSize = DEFAULT_MEMITEM_SIZE);
void* _GetMemBlock(size_t iMemBlockSize = DEFAULT_MEMBLOCK_SIZE,size_t iMemItemSize = DEFAULT_MEMITEM_SIZE);
bool _FreeMemBlock(_tMemBlock* pDesBlock);
_tMemBlock* _AddNewBlock(size_t iMemBlockSize = DEFAULT_MEMBLOCK_SIZE,size_t iMemItemSize = DEFAULT_MEMITEM_SIZE);
public:
CMemPool(size_t iMemBlockSize = DEFAULT_MEMBLOCK_SIZE,size_t iMemItemSize = DEFAULT_MEMITEM_SIZE,bool bWriteLog = false);
virtual ~CMemPool();
public:
void* AllocMem(size_t iItemSize);
bool FreeMem(void *pDesItem);
void DestroyMemPool();
void SetWriteLog(bool bWriteLog,char* LogFilePath = "C:\\MemPoolLog.txt");//是否记录内存池使用日志,true为写日志,false为不写日志
bool WriteLog(char* pLogContent);
bool DisplayBlockMsg(_tMemBlock* pNewBlock);
};
申请内存单元:当用户调用AllocMem申请内存单元时,首先遍历内存池列表,看是否有合适的内存单元用于分配,如果有则进行分配;否则调用_AddNewBlock从堆中申请内存块,如果申请内存块成功,则从该内存块中分配内存单元。
释放内存单元:当用户调用FreeMem释放内存单元时,首先遍历内存池列表,并判断将要释放的内存单元属于哪个内存块,然后就可以调用内存单元管理程序完成内存单元的回收,在内存单元回收过程中,如果内存单元所在的内存块已经全部为未使用状态,则释放该内存块入堆中。