所谓内存池,是指,应用程序可以通过系统的内存分配调用预先一次性申请适当大小的内存作为一个内存池,之后应用程序自己对内存的分配和释放则可以通过这个内存池来完成。只有当内存池大小需要动态扩展时,才需要再调用系统的内存分配函数,其他时间对内存的一切操作都在应用程序的掌控之中。
应用程序自定义的内存池根据不同的适用场景又有不同的类型----
从线程安全的角度来分,内存池可以分为单线程内存池和多线程内存池。单线程内存池整个生命周期只被一个线程使用,因而不需要考虑互斥访问的问题;多线程内存池有可能被多个线程共享,因此则需要在每次分配和释放内存时加锁。相对而言,单线程内存池性能更高,而多线程内存池适用范围更广。
从内存池可分配内存单元大小来分,可以分为固定内存池和可变内存池。所谓固定内存池是指应用程序每次从内存池中分配出来的内存单元大小事先已经确定,是固定不变的;而可变内存池则每次分配的内存单元大小可以按需变化,应用范围更广,而性能比固定内存池要低。
具体的介绍可见IBM这篇文章:
前段时间有空余,就写了段基本的内存池实现,暂不支持多线程,且是固定内存池。大家想参考这片文章来实现的,可以看看我这里的代码。
ps:很感谢小赖帮我调试出了内存回收的bug,有的时候写一段代码只要1,2小时,但是找bug可能真的得1天。
memory_pool.h
#ifndef MEMORY_POOL_H
#define MEMORY_POOL_H
/***********************************
* author:hairetzhang *
* date:2009.9.1 *
* funtion: regular memory pool lib *
* version 1.0 *
* *
************************************/
#include <iostream>
#include <malloc.h>
using namespace std;
#define USHORT unsigned short
#define ULONG unsigned long
#define ADATAUNIT 2
#define MEMPOOL_ALIGNMENT 4 //机器的字节对齐数
struct MemoryBlock
{
MemoryBlock* pNext;
USHORT nSize; //每个block的总小块数
USHORT nfree_left; //剩余小块的数目
USHORT nfirst_index; //第一个可用小块的索引号
USHORT aData[1]; //第一个小块前一个字节,不被使用
//nSize = nTypes; //记录分配的小块数
//pNext = NULL;
static void* block_new( USHORT nTypes, USHORT nUnitSize)
{
MemoryBlock* my_block;
my_block = (MemoryBlock *) malloc(sizeof(MemoryBlock) + nTypes * nUnitSize);
cout << "sizeof block is :" << sizeof(MemoryBlock) <<endl;
my_block->nSize = nTypes; //申请时,块内初始化
my_block->nfree_left = nTypes;
my_block->nfirst_index = 0;
my_block->pNext = NULL;
return my_block;
}
static void block_delete(void *p )
{
free(p);
}
MemoryBlock (USHORT nTypes = 1, USHORT nUnitSize = 0);
~MemoryBlock();
};
class MemoryPool
{
private:
MemoryBlock* pBlock;
USHORT nUnitSize;
USHORT nInitSize;
USHORT nGrowSize;
public:
MemoryPool( USHORT nUnitSize,
USHORT nInitSize = 1024,
USHORT nGrowSize = 256 );
~MemoryPool();
void* Alloc();
void Free( void* p );
void init_block(MemoryBlock* new_block , USHORT nSize);
void* get_pblock(); //用于调试
};
#endif
memory_pool.cpp
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include "memory_pool.h"
using namespace std;
MemoryPool::MemoryPool( USHORT _nUnitSize,
USHORT _nInitSize, USHORT _nGrowSize )
{
pBlock = NULL;
nInitSize = _nInitSize; //每个块分配的索引小块数
nGrowSize = _nGrowSize; //增长块的内存索引小块数
//设置小块的大小
if ( _nUnitSize > 4 )
nUnitSize = (_nUnitSize + (MEMPOOL_ALIGNMENT-1)) & ~(MEMPOOL_ALIGNMENT-1);
else if ( _nUnitSize <= 2 )
nUnitSize = 2;
else
nUnitSize = 4;
}
void MemoryPool::init_block( MemoryBlock* new_block ,USHORT nSize ) //初始化分配的新块,nSize为该块的总索引数
{
char *pdata = (char * )new_block->aData; //从第二个小块开始设置索引,第一个小块返回提供使用
USHORT *mini_block_head;
for(int i = 1 ;i < nSize; ++i) //设置每个小块的索引头,0号返回给该次申请者使用
{
mini_block_head = (USHORT *)(pdata+nUnitSize*i +ADATAUNIT);
*mini_block_head = i+1;
}
new_block->nfirst_index = 1;
new_block->nfree_left = nSize-1;
}
void* MemoryPool::Alloc()
{
if( !pBlock ) //当前内存池尚无内存块,第一次分配
{
pBlock = (MemoryBlock *) MemoryBlock::block_new (nInitSize, nUnitSize);
init_block( pBlock ,nInitSize);
return (void *) (pBlock->aData+1);
}
MemoryBlock* pMyBlock = pBlock; //从链表头部pBlock开始
while (pMyBlock && !pMyBlock->nfree_left ) //搜索可用内存快
pMyBlock = pMyBlock->pNext;
if ( pMyBlock ) //尚有可用的内存快
{
char* pFree = (char *) ( (ULONG)pMyBlock->aData+( pMyBlock->nfirst_index*nUnitSize)+ADATAUNIT );
printf("pMyBlock->aData:%p, pFree:%p/n",pMyBlock->aData,pFree);
pMyBlock->nfirst_index = *( (USHORT *)pFree ); //每小块的头部两字节记录了下一个可用索引号
pMyBlock->nfree_left--;
return (void*)pFree;
}
else //找不到可用内存块
{
if ( !nGrowSize )
return NULL;
pMyBlock = (MemoryBlock *)MemoryBlock::block_new (nGrowSize, nUnitSize);
if ( !pMyBlock )
return NULL;
pMyBlock->pNext = pBlock; //新块插入到块链表头部
pBlock = pMyBlock;
init_block( pMyBlock ,nGrowSize);
return (void*)(pMyBlock->aData+ADATAUNIT);
}
}
void MemoryPool::Free( void* pFree )
{
USHORT* pfree_us;
USHORT pfree_index, pHead;
pfree_us = (USHORT *)pFree;
MemoryBlock* pMyBlock = pBlock;
MemoryBlock* pre_pBlock = NULL;
while ( ((ULONG)pMyBlock->aData > (ULONG)pfree_us) ||
((ULONG)pfree_us >= ((ULONG)pMyBlock->aData + pMyBlock->nSize*nUnitSize)) ) //定位pFree所在的块
{
pre_pBlock = pMyBlock;
pMyBlock = pMyBlock->pNext;
}
if(pMyBlock == NULL) //pFree不是属于内存池管理的内存
return;
//回收pFree
pMyBlock->nfree_left++;
pHead = pMyBlock->nfirst_index; //第一个可用索引
pfree_index = ( (ULONG)pfree_us-(ULONG)pMyBlock->aData -ADATAUNIT)/nUnitSize; //获取pFree的索引号
pMyBlock->nfirst_index = pfree_index; //pFree插入到可用块首
*pfree_us = pHead; //之前的块首链入
//判断是否需要将Block内存返回给系统
if (pMyBlock->nfree_left == pMyBlock->nSize )
{
if(pre_pBlock) //删除的不是第一个MemoryBlock
pre_pBlock->pNext = pMyBlock->pNext;
pBlock = pMyBlock->pNext; //删除的是第一个MemoryBlock
MemoryBlock::block_delete(pMyBlock);
}
}
MemoryPool::~MemoryPool() //析构函数释放所有的MemoryBlock,即使用户忘记free掉自己申请的内存也不会发生内存泄漏
{
MemoryBlock* my_block = pBlock;
MemoryBlock* next_block = NULL;
while( my_block )
{
next_block = my_block->pNext;
MemoryBlock::block_delete( (void *)my_block);
my_block = next_block;
}
}
void* MemoryPool::get_pblock()
{
return pBlock;
}
作者:hairetz