1. 内存池介绍
内存池是预分配一段连续的内存,然后将它们划分成固定大小块,以便稍后快速分配和释放。内存池主要应用于程序在运行过程中经常申请和释放分配内存的场景。使用内存池可以有效地减少分配和释放内存的时间成本,从而提高程序的执行效率。
内存池有两种方案,一种是大小可变的内存池,一种是大小固定的内存池。大小可变的内存池,各个内存块通过链表连接,空闲的内存块位于链表前段。当程序需要一段内存时,就以此检查可用的内存块,如果大小满足要求,就利用当前这块内存。这里可能会出现一个较小的内存需要缺占据了一个之前动态分配现在已空闲出来的较大内存。
大小固定的内存池,可以开始分配一段大的内存,然后切割成等大小的多块内存。然后用一个额外的链表,记录空闲的内存块。当需要内存时,就从空闲的内存块里查找即可,因为内存块大小固定,因此根据偏移量容易定位内存位置,同时释放整个大内存块也很方便。缺点是这个每段内存块的大小是多少合适很难确定。
本文实现的内存池是结合以上两种情况的优点,初始化时分配多段大内存,然后切分成128,256,512等字节大小的小内存块。而每个大小的内存块如果有多块,又采用一个链表来管理。当需要一个内存时,首先根据内存大小需求到那个值的内存块里去获取,然后遍历该内存块的管理链表,从而获得自己需要的内存。
2. 核心代码
memoryPool.h:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "memoryPool.h"
using namespace SgLib;
int main(int argc, char *argv[])
{
// false表示线程不安全,不需要同步内存块的锁,效率更高。如果是true,用于线程安全
MemoryPool g_mp(false);
// 初始内存池对象
// 3:表示初始创建128,256,521三个层级的内存块,2表示每个层级内存块里创建2个内存数据块
g_mp.init(3, 2);
char *pszTmp1 = (char *)g_mp.allocMemory(1000);
strcpy(pszTmp1, "我爱北京天安门");
printf("pszTmp1内存地址:0x%lu, 内容:%s\n", (unsigned long)(uintptr_t)pszTmp1, pszTmp1);
g_mp.printInfo();
char *pszTmp2 = (char *)g_mp.allocMemory(10000);
strcpy(pszTmp2, "I am a student.我是一个学生");
printf("pszTmp2内存地址:0x%lu, 内容:%s\n", (unsigned long)(uintptr_t)pszTmp2, pszTmp2);
g_mp.printInfo();
g_mp.freeMemory(pszTmp1);
g_mp.printInfo();
char *pszTmp3 = (char *)g_mp.allocMemory(900);
strcpy(pszTmp3, "ppp");
printf("pszTmp3内存地址:0x%lu, 内容:%s\n", (unsigned long)(uintptr_t)pszTmp3, pszTmp3);
g_mp.printInfo();
g_mp.freeMemory(pszTmp1);
g_mp.freeMemory(pszTmp2);
g_mp.printInfo();
return 0;
}
在实现文件里,最核心的就是创建内存块函数:
void MemoryPool::createOneBlockMemory(size_t siLevel)
{
size_t siNums = m_siInitNums;
size_t siBlockSize = pow(2, 6 + (siLevel + 1));
if (siBlockSize > 1048576)
{
siNums = 1;
}
// 每个数据块前是控制块,便于释放时寻找
void *pData = malloc((siBlockSize + sizeof(tFreeDataBlock)) * siNums);
if (pData == NULL)
{
return;
}
else
{
byte *pbtData = (byte *)pData;
tSrcDataBlock *pSrc = new tSrcDataBlock();
pSrc->pNext = m_pSrcBlockHead[siLevel];
pSrc->pDataPos = pbtData;
m_pSrcBlockHead[siLevel] = pSrc;
tFreeDataBlock *pPreFree = NULL;
for (size_t m = 0; m < siNums; ++m)
{
tFreeDataBlock *pFree = (tFreeDataBlock *)(pbtData + (int)(m * (siBlockSize + sizeof(tFreeDataBlock))));
pFree->pDataPos = (byte *)pFree + sizeof(tFreeDataBlock);
pFree->pNext = NULL;
pFree->siLevel = siLevel;
if (m == 0)
{
m_pFreeBlockHead[siLevel] = pFree;
}
if (pPreFree != NULL)
{
pPreFree->pNext = pFree;
}
pPreFree = pFree;
}
}
}
3. 调用内存池类
如何调用内存池类:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "memoryPool.h"
using namespace SgLib;
MemoryPool g_mp(true);
int main(int argc, char *argv[])
{
// 初始内存池对象
g_mp.init(2, 2);
char *pszTmp1 = (char *)g_mp.allocMemory(1000);
strcpy(pszTmp1, "我爱北京天安门");
printf("pszTmp1内存地址:0x%lu, 内容:%s\n", (unsigned long)(uintptr_t)pszTmp1, pszTmp1);
g_mp.printInfo();
char *pszTmp2 = (char *)g_mp.allocMemory(10000);
strcpy(pszTmp2, "I am a student.我是一个学生");
printf("pszTmp2内存地址:0x%lu, 内容:%s\n", (unsigned long)(uintptr_t)pszTmp2, pszTmp2);
g_mp.printInfo();
g_mp.freeMemory(pszTmp1);
g_mp.printInfo();
char *pszTmp3 = (char *)g_mp.allocMemory(900);
strcpy(pszTmp3, "ppp");
printf("pszTmp3内存地址:0x%lu, 内容:%s\n", (unsigned long)(uintptr_t)pszTmp3, pszTmp3);
g_mp.printInfo();
g_mp.freeMemory(pszTmp1);
g_mp.freeMemory(pszTmp2);
g_mp.printInfo();
return 0;
}
4. 工程运行和截图
从截图中,可以看到pszTmp1和pszTmp3的内存地址是一样的,即pszTmp3重用了pszTmp1的内存块(pszTmp1“释放”后)。
完整代码的下载可以参考下面网址:
http://kbase12.com/codedemo/doc/detail?id=80431cdaaf99472c97a1e99547798307