内存池的C语言实现

在某些情况下,需要反复申请和释放大量固定大小的小块内存,如果利用malloc和free的话不但效率低下,而且会使系统产生大量的内存碎片。此时,大都选择使用内存池(Memory Pool)来提高效率。

内存池的原理就是事先申请好一大块内存,然后再在这块内存上分配和释放小块内存。由于允许申请的内存块大小固定,因此不会产生,也不会有分割合并内存块的开销。

下面是我正在编写的GUI系统中所使用的内存池。

首先来看内存池的结构定义:

typedef struct tagMEMPOOL MEMPOOL, *LPMEMPOOL; struct tagMEMPOOL { DWORD dwBlockSize; // 内存池允许分配的内存块的大小 DWORD dwBlockNumber; // 内存池中内存块的数量 DWORD dwFree; // 第一块空闲内存的索引 LPVOID lpData; // 事先申请的大块内存的首地址 };  

创建内存池:

HMEMPOOL TWINAPI CreateMemPool( DWORD dwBlockSize, DWORD dwBlockNumber ) { // 将dwBlockSize增加一个DWORD的空间来存放内存块状态 dwBlockSize += SIZEOF(DWORD); // 申请内存 LPMEMPOOL lpMemPool = (LPMEMPOOL)MemAlloc(SIZEOF(MEMPOOL) + dwBlockSize * dwBlockNumber); if(!lpMemPool) return NULL; // 填写MEMPOOL结构 lpMemPool->dwBlockNumber = dwBlockNumber; lpMemPool->dwBlockSize = dwBlockSize; lpMemPool->dwFree = 0x00000000UL; lpMemPool->lpData = (LPVOID)((LPBYTE)lpMemPool + SIZEOF(MEMPOOL)); // 将MEMPOOL结构的指针转换成句柄返回 return (HMEMPOOL)lpMemPool; }  

由于需要用一个DWORD值来保存内存块的状态,所以在分配内存前先将内存块的大小增加一个DWORD的空间。让后向系统申请内存,并填写MEMPOOL结构。

从内存池中分配内存:

// 内存块状态 #define BH_FREE 0x00000000UL #define BH_USED 0x00000001UL #define BH_HEAP 0x00000002UL LPVOID TWINAPI MemPoolAlloc( HMEMPOOL hMemPool ) { // 计算出dwFree索引的内存块 LPBYTE lpBlock = ((LPMEMPOOL)hMemPool)->lpData + ((LPMEMPOOL)hMemPool)->dwFree * ((LPMEMPOOL)hMemPool)->dwBlockSize; DWORD i; // 从dwFree索引的内存块开始向后查找空闲的内存块 for(i = ((LPMEMPOOL)hMemPool)->dwFree; i < ((LPMEMPOOL)hMemPool)->dwBlockNumber; ++ i) { // 找到空闲的内存块,将其状态标记为BH_USED并返回 if(*((LPDWORD)(lpBlock)) == BH_FREE) { *((LPDWORD)(lpBlock)) = BH_USED; return (LPVOID)(lpBlock + SIZEOF(DWORD)); } lpBlock += ((LPMEMPOOL)hMemPool)->dwBlockSize; } // 内存池中没有空闲的内存,尝试在堆上分配 lpBlock = MemAlloc(((LPMEMPOOL)hMemPool)->dwBlockSize); if(!lpBlock) return NULL; // 将内存块状态标记为BH_HEAP并返回 *((LPDWORD)(lpBlock)) = BH_HEAP; return (LPVOID)(lpBlock + SIZEOF(DWORD)); } 

首先通过判断每个内存块头部的DWORD值来找出空闲的内存块。找到以后将其头部的DWORD标记为BH_USED,在将内存块的指针向后偏移一个DWORD后返回。如果没有在内存池中返回,那么就在堆上分配内存块,将状态标记为BH_HEAP后向后偏移一个DWORD并返回。

释放内存块:

VOID TWINAPI MemPoolFree( HMEMPOOL hMemPool, LPVOID lpData ) { // 将内存块指针向前偏移一个DWORD LPBYTE lpBlock = lpData - SIZEOF(DWORD); DWORD i; // 检查内存块的状态 switch(*((LPDWORD)(lpBlock))) { case BH_HEAP: // 内存块在堆上,直接释放到堆上 MemFree(lpBlock); return; case BH_USED: // 内存块在内存池中,将内存块状态标记为BH_FREE *((LPDWORD)(lpBlock)) = BH_FREE; // 计算出该内存块的索引,如果在dwFree之前,则将dwFree指向这块内存 i = ((DWORD)lpBlock - (DWORD)((LPMEMPOOL)hMemPool)->lpData) / ((LPMEMPOOL)hMemPool)->dwBlockSize; if(((LPMEMPOOL)hMemPool)->dwFree > i) ((LPMEMPOOL)hMemPool)->dwFree = i; return; default: // 其它情况忽略 return; } } 

释放内存块时将内存块指针向前偏移一个DWORD以获得实际内存块的指针,判断头部DWORD值并根据不同值进行相应的释放动作。若果内存块在堆上,则直接释放到堆上,如果在内存池里,则将内存块状态标记为BH_FREE,并计选出该内存块的索引。如果索引在dwFree之前,则将dwFree设为这块内存的索引,这样使得每次释放后dwFree中保存的都是第一块空闲内存的索引,提高了下一次分配的速度。

释放内存池:

VOID TWINAPI DeleteMemPool( HMEMPOOL hMemPool ) { MemFree(hMemPool); } 

对于多线程的情况,还应该在MEMPOOL结构中增加一个互斥锁。此外,还何以在MEMPOOL结构中增加两个函数指针来实现带构造和析沟和数的内存池。

 

备注:

由于正在编写的GUI是参考Windows的体系结构,所以代码风格采用了微软的风格。

代码中的MemAlloc,MemFree函数以及各种数据结构均在其它地方定义。

你可能感兴趣的:(内存池的C语言实现)