内存池-小应用
在编写程序的时候,内存泄漏问题很关键。如果程序运行一次产生了一些小的泄漏,那么最终会导致程序崩溃。定位这种问题的来源也比较麻烦。因此对所有的分配和释放内存的操作都进行跟踪则能有效的解决这类问题。
在程序开始运行时,给程序初始分配一个大的内存空间。在每一次分配内存空间的时候,都用这个内存空间进行分配。其初始化过程如下:
分配一个空间,并用表头来记录这个空间段。表头包括:该段的空间大小,下一个段,上一个段,下一个未使用的段和上一个未使用的段。
多次分配和释放后的内存状态如下:
深颜色表示空间已分配,浅颜色表示未分配。在每次进行内存分配的过程中,也创建一个表头来记录这个空间段,并维护它与上、下段之间的链表关系。以0X10为例,它的上一个段为0X00,下一个段为0X30,下一个未分配段为0X40。
以下为表头定义:
typedef struct ST_MEMPOOL_TAG{
struct ST_MEMPOOL_TAG * pstPrev; //指向前一个段
struct ST_MEMPOOL_TAG * pstNext; //指向后一个段
int nSize; //当前段的SIZE,不包括自已本身
struct ST_MEMPOOL_TAG * pstNextUnused; //指向下一个空闲的段
struct ST_MEMPOOL_TAG * pstPrevUnused; //指向上一个安闲的段
int nUsedFlag; //这个段是否使用的标记位
} ST_Mempool, *PST_Mempool;
// 一个结构体
为了能记录内存段的初始位置以及未分配的内在段,定义两个全局变量:
static ST_Mempool * g_pstMempoolStart = NULL;//起始的内存段
static ST_Mempool * g_pstMempoolUnused = NULL;//起始的未使用的段
并且定义两个宏:
#define FREE(x) MempoolFree(x) //定义两个宏
#define MALLOC(x) MempoolMalloc(x) //定义两个宏
为了维护链表,删除节点和增加节点的步骤如下:
//增加节点操作, nMode == 0表示pstNew 在节点pstFirst之后,== 1表示在节点之前
void MempoolAddNode(ST_Mempool* pstFirst, ST_Mempool * pstNew, int nMode)
{
if(0 == nMode)
{
pstNew->pstNext = pstFirst->pstNext;
pstNew->pstPrev = pstFirst;
pstFirst->pstNext = pstNew;
pstNew->pstNext->pstPrev = pstNew;
}
else
{
pstNew->pstPrev = pstFirst->pstPrev;
pstNew->pstNext = pstFirst;
pstFirst->pstPrev = pstNew;
pstNew->pstPrev->pstNext = pstNew;
}
}
//增加节点操作, nMode == 0表示pstNew 在节点pstFirst之后,== 1表示在节点之前
void MempoolAddUnused(ST_Mempool* pstFirst, ST_Mempool * pstNew, int nMode)
{
if(0 == nMode)
{
pstNew->pstNextUnused = pstFirst->pstNextUnused;
pstNew->pstPrevUnused = pstFirst;
pstFirst->pstNextUnused = pstNew;
pstNew->pstNextUnused->pstPrevUnused = pstNew;
}
else
{
pstNew->pstPrevUnused = pstFirst->pstPrevUnused;
pstNew->pstNextUnused = pstFirst;
pstFirst->pstPrevUnused = pstNew;
pstNew->pstPrevUnused->pstNextUnused = pstNew;
}
}
//删除节点操作
void MempoolDeleteNode(ST_Mempool * pstDel)
{
pstDel->pstPrev->pstNext = pstDel->pstNext;
pstDel->pstNext->pstPrev = pstDel->pstPrev;
}
//删除节点操作
void MempoolDeleteUnused(ST_Mempool * pstDel)
{
pstDel->pstPrevUnused->pstNextUnused = pstDel->pstNextUnused;
pstDel->pstNextUnused->pstPrevUnused = pstDel->pstPrevUnused;
}
初始化时,只有一个表头,因此,它的上、下段和上、下未分配段都是它自身,如下:
//初始化内存池,内存池的总大小为nSize
void MempoolInit(int nSize)
{
//申请一个nSize的内存块
void * tmp = (void *)malloc(nSize);
memset(tmp, 0, nSize);
//初始化
g_pstMempoolStart = (ST_Mempool*) tmp;
g_pstMempoolUnused = (ST_Mempool*)tmp;
g_pstMempoolStart->nSize = nSize - sizeof(ST_Mempool);
//环状链条
g_pstMempoolStart->pstNextUnused = g_pstMempoolStart;
g_pstMempoolStart->pstNext = g_pstMempoolStart;
g_pstMempoolStart->pstPrev = g_pstMempoolStart;
g_pstMempoolStart->pstPrevUnused = g_pstMempoolStart;
}
分配内存空间时,则直接从未分配内存段开始查找,直到找到可以分配的段为止:
//分配内存空间
void * MempoolMalloc(int nSize)
{
ST_Mempool * pstTmp = g_pstMempoolUnused;
do
{
//如果当前段可以容得下
if(pstTmp->nSize >= nSize + sizeof(ST_Mempool))
{
//创建一个新的片段,用以维护空白区域
ST_Mempool * pstNew = (ST_Mempool * )((char *)pstTmp + nSize + sizeof(ST_Mempool));
//维护新的片段
MempoolAddNode(pstTmp, pstNew, 0);
pstNew->nSize = pstTmp->nSize - nSize - sizeof(ST_Mempool);
pstNew->nUsedFlag = 0;
//把它放到未分配的链表中
MempoolAddUnused(pstTmp, pstNew, 0);
//再把这个段从未分配链表中删除
MempoolDeleteUnused(pstTmp);
//更新以前的片段
pstTmp->nSize = nSize;
pstTmp->nUsedFlag = 1;
//如果就是在空白起始的位置修改了,则修改
if(g_pstMempoolUnused == pstTmp)
{
g_pstMempoolUnused = pstNew;
}
//返回位置
return (char *) pstTmp + sizeof(ST_Mempool);
break;
}
else //否则,往后查找
{
pstTmp = pstTmp->pstNextUnused;
}
}
while(pstTmp != g_pstMempoolUnused);
//分配失败
return NULL;
}
而删除操作则比较复杂,一方面,需要将它的标记位置为0,另一方面,如果它的上、下节点都是未使用,则可以进行合并操作,如下:
//删除内存空间
void MempoolFree(void * pAddress)
{
//需要删除的节点
ST_Mempool * pstTmp = (ST_Mempool *)((char *)pAddress - sizeof(ST_Mempool));
//更新这个节点
pstTmp->nUsedFlag = 0;
ST_Mempool * pstUnused = g_pstMempoolUnused;
//找到这个节点在未使用的位置
while(pstUnused < pstTmp)
{
pstUnused = pstUnused->pstNextUnused;
}
//更新这个节点
MempoolAddUnused(pstUnused, pstTmp, 1);
//合并节点,如果后面的一个是空
if(0 == pstTmp->pstNext->nUsedFlag && pstTmp->pstNext != g_pstMempoolStart)
{
pstTmp->nSize = pstTmp->nSize + sizeof(ST_Mempool) + pstTmp->pstNext->nSize;
pstTmp->pstNext = pstTmp->pstNext->pstNext;
pstTmp->pstNext->pstPrev = pstTmp;
pstTmp->pstNextUnused = pstTmp->pstNextUnused->pstNextUnused;
pstTmp->pstNextUnused->pstPrevUnused = pstTmp;
}
//合并节点,如果前面一个是空的
if(0 == pstTmp->pstPrev->nUsedFlag && pstTmp != g_pstMempoolStart)
{
pstTmp->pstPrev->nSize = pstTmp->nSize + sizeof(ST_Mempool) + pstTmp->pstPrev->nSize;
pstTmp->pstPrev->pstNext = pstTmp->pstNext;
pstTmp->pstNext->pstPrev = pstTmp->pstPrev;
pstTmp->pstPrevUnused->pstNextUnused = pstTmp->pstNextUnused;
pstTmp->pstNextUnused->pstPrevUnused = pstTmp->pstPrevUnused;
pstTmp = pstTmp->pstPrev;
}
//更新起始节点
if(pstTmp < g_pstMempoolUnused)
{
g_pstMempoolUnused = pstTmp;
}
}
同样的,为了调试这个程序,找印内存段中的内容,如下:
void PrintfMempool()
{
printf("====================================================\n");
ST_Mempool * pstTmp = g_pstMempoolStart;
do
{
printf("Address: %0x, nSize: %d, nRealSize: %d, nUsedFlag: %d\n", pstTmp, pstTmp->nSize, -(int)pstTmp + (int)pstTmp->pstNext - 24, pstTmp->nUsedFlag);
pstTmp = pstTmp->pstNext;
}while(pstTmp != g_pstMempoolStart);
pstTmp = g_pstMempoolUnused;
printf("-------------------------\n");
do
{
printf("Address: %0x, nSize: %d, nUsedFlag: %d\n", pstTmp, pstTmp->nSize, pstTmp->nUsedFlag);
pstTmp = pstTmp->pstNextUnused;
}while(pstTmp != g_pstMempoolUnused);
}
主函数如下:
int main(int argc, char* argv[])
{
MempoolInit(1000);
int * a1 = (int *) MALLOC(2);
int * a2 = (int *) MALLOC(3);
int * a3 = (int *) MALLOC(4);
int * a4 = (int *) MALLOC(5);
int * a5 = (int *) MALLOC(6);
FREE(a1);
FREE(a3);
FREE(a4);
PrintfMempool();
getchar();
return 0;
}