这是我在VS2019上写的第一个项目,使用VS2019的目的是想在更为规范的IDE上写出更加规范的代码。
使用内存池可以减少程序运行中产生的内存碎片,且可以提高程序内存分配效率从而提升程序效率。在这篇笔记中,我将记录下自己关于这个内存池项目的思路与详细代码。同时,在我的C++网络编程学习相关内容的下一步改进中,我将引入这个内存池提高服务端的运行效率。
一、内存池设计思路
首先,为什么要使用内存池?
我是这样理解的:不断的使用new/malloc
从堆中申请内存,会在内存中留下一些“缝隙”。例如我们申请三份8个字节大小的内存A、B、C,由于内存地址是连续的,则ABC的地址值每个相差8(正常情况)。此时我们delete/free
掉B内存,A与C内存之间此时就有了8个字节的空白。假如我们今后申请的内存都比8个字节大,则A与C之间这块内存就会一直为空白,这就是内存碎片。
过多的内存碎片会影响程序的内存分配效率,为了降低内存碎片的影响,我们可以引入内存池来尝试解决它。
我们可以在程序启动时(或是其他合适的时机),预先申请足够的、大小相同的内存,把这些内存放在一个容器内。在需要申请内存时,直接从容器中取出一块内存使用;而释放内存时,把这块内存放回容器中即可。这个容器就被称为内存池。而这样操作也可以大大减少内存碎片出现的可能性,提高内存申请/释放的效率。
这个项目中内存池的思路图如下:

我们需要新建三个类:
- 首先是底层的内存块类,其中包含了该内存块的信息:内存块编号、引用情况、所属内存池、下一块的位置等。
- 其次是内存池类,它对成组的内存块进行管理,可以实现把内存块从内存池中取出以及把内存块放回内存池。
- 最后是内存管理工具类,其中包含一个或多个内存池,所以它要根据用户申请的内存大小找到合适的内存池,调用内存池类的方法申请/释放内存。
还需要进行的操作:
- 对
new/delete
进行重载,使其直接调用内存管理工具类申请/释放内存。
上面的工作完成后,我们仍是以new/delete
来申请/释放内存,但是已经是通过内存池来实现的了,这个内存池项目也就暂时结束。下面我将详细记录实现的过程与思路。
二、内存块类MemoryBlock
设计与实现
先扔出来思路图:

首先,在内存池中每一块内存是由一个内存头以及其可用内存组成的,其中内存头里储存了这块内存的相关信息,可用内存即为数据域,类似链表中节点的结构。而一块块内存之间正是一种类似链表的结构,即通过内存头中的一个指针进行连接。内存头中包含的信息大概如下:
- 1、内存块编号
- 2、引用情况
- 3、所属内存池
- 4、下一块位置
- 5、是否在内存池内
则我们可以通过上面的思路新建内存块类MemoryBlock
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 由于内存头中要标记所属内存池,所以我们先预声明内存池类,在之后再进行实现。 建立完成后,内存池内一块内存的大小为:sizeof(MemoryBlock) + 可用内存的大小
class MemoryAlloc;
class MemoryBlock { public: int _nID; int _nRef; MemoryAlloc* _pAlloc; MemoryBlock* _pNext; bool _bPool;
private:
};
|
三、内存池类MemoryAlloc
设计与实现
还是先扔出来内存池申请/释放内存的思路图:

由图可知,整个内存池的管理基本为链表结构,内存池对象一直指向头部内存单元。在申请内存时移除头部单元,类似链表头结点的移除;在释放内存时,类似链表的头插法,把回收回来的内存单元放在内存池链表的头部。
内存池类中大概包含这些东西:
1、方法
- 1.成员变量初始化 —— 对内存单元可用内存大小以及内存单元数量进行设定
- 2.初始化 —— 依据内存单元的大小与数量,对内存池内的内存进行
malloc
申请,完善每一个内存单元的信息
- 3.申请内存 —— 从内存池链表中取出一块可用内存
- 4.释放内存 —— 将一块内存放回内存池链表中
2、成员变量
- 1.内存池地址 —— 指向内存池内的总内存
- 2.头部内存单元 —— 指向头部内存单元
- 3.内存块大小 —— 内存单元的可用内存大小
- 4.内存块数量 —— 内存单元的数量
则我们可以通过上面的思路新建内存块类MemoryAlloc
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include"MemoryBlock.h"
class MemoryAlloc { public: MemoryAlloc(); virtual ~MemoryAlloc(); void setInit(size_t nSize,size_t nBlockSize); void initMemory(); void* allocMem(size_t nSize); void freeMem(void* p);
protected: char* _pBuf; MemoryBlock* _pHeader; size_t _nSize; size_t _nBlockSize; std::mutex _mutex; };
|
四、内存管理工具类MemoryMgr
设计与实现
仍然是先放思路图:

首先,内存管理工具类用的是单例对象模式,从而能简易的对内存池进行管理。在这次的实现里,我使用的是饿汉式单例对象。其次,为了更简单的判断出申请内存时所需要调用的内存池,我建立了一个数组映射内存池。在工具类构造函数内,首先是对内存池进行初始化,随后便是将其映射到数组上。
1 2 3 4
| 映射: 假如申请一个64字节内存池,申请一个128字节内存池 我们新建一个指针数组test,使下标0~64指向64字节内存池,下标65~128指向128字节内存池 则我们通过 test[要申请的内存大小] 即可确定合适的内存池
|
在随后的申请过程中,我们首先判断申请内存大小是否超过内存池最大可用内存,若没超过,则通过映射数组指向的内存池进行内存申请;若超过了,则直接使用malloc
申请,记得多申请一个内存头大小的内存。随后完善内存头内的资料。
在随后的释放过程中,我们通过内存头判断这块内存是否使属于内存池的内存,如果是,则通过其所属内存池进行内存回收;若不是,则直接进行free
释放。
内存管理工具类中大概包含这些东西:
1、方法
- 饿汉式单例模式 —— 调用返回单例对象
- 申请内存 —— 调用获取一块内存
- 释放内存 —— 调用释放一块内存
- 内存初始化 —— 将内存池映射到数组上
2、成员变量
- 映射数组 —— 映射内存池
- 内存池1
- 内存池2
- 内存池…
则我们可以通过上面的思路新建内存管理工具类MemoryMgr
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #define MAX_MEMORY_SIZE 128
#include"MemoryAlloc.h"
class MemoryMgr { public: static MemoryMgr* Instance(); void* allocMem(size_t nSize); void freeMem(void* p); private: MemoryMgr(); virtual ~MemoryMgr(); void init_szAlloc(int begin,int end,MemoryAlloc* pMem);
private: MemoryAlloc* _szAlloc[MAX_MEMORY_SIZE + 1]; MemoryAlloc _mem64; MemoryAlloc _mem128; };
|
五、重载new/delete
重载new/delete
就不多说了,直接放代码:
1 2 3 4 5 6
| void* operator new(size_t size); void operator delete(void* p); void* operator new[](size_t size); void operator delete[](void* p); void* mem_alloc(size_t size); void mem_free(void* p);
|
六、项目代码及其注释
1.项目图片
)
2.重载new/delete
2.1 Alloctor.h
1 2 3 4 5 6 7 8 9 10 11
| #ifndef _Alloctor_h_ #define _Alloctor_h_
void* operator new(size_t size); void operator delete(void* p); void* operator new[](size_t size); void operator delete[](void* p); void* mem_alloc(size_t size); void mem_free(void* p);
#endif
|
2.2 Alloctor.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include"Alloctor.h" #include"MemoryMgr.h"
void* operator new(size_t size) { return MemoryMgr::Instance()->allocMem(size); }
void operator delete(void* p) { MemoryMgr::Instance()->freeMem(p); }
void* operator new[](size_t size) { return MemoryMgr::Instance()->allocMem(size); }
void operator delete[](void* p) { MemoryMgr::Instance()->freeMem(p); }
void* mem_alloc(size_t size) { return MemoryMgr::Instance()->allocMem(size); }
void mem_free(void* p) { MemoryMgr::Instance()->freeMem(p); }
|
3.内存池类MemoryAlloc
3.1 MemoryAlloc.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
#ifndef Memory_Alloc_h #define Memory_Alloc_h
#include"MemoryBlock.h"
class MemoryAlloc { public: MemoryAlloc(); virtual ~MemoryAlloc(); void setInit(size_t nSize,size_t nBlockSize); void initMemory(); void* allocMem(size_t nSize); void freeMem(void* p);
protected: char* _pBuf; MemoryBlock* _pHeader; size_t _nSize; size_t _nBlockSize; std::mutex _mutex; };
#endif
|
3.2 MemoryAlloc.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
| #include"MemoryAlloc.h"
MemoryAlloc::MemoryAlloc() { _pBuf = nullptr; _pHeader = nullptr; _nSize = 0; _nBlockSize = 0; }
MemoryAlloc::~MemoryAlloc() { if (_pBuf) { free(_pBuf); } }
void MemoryAlloc::setInit(size_t nSize, size_t nBlockSize) {
_pBuf = nullptr; _pHeader = nullptr; _nSize = nSize; _nBlockSize = nBlockSize; initMemory(); }
void MemoryAlloc::initMemory() { assert(nullptr == _pBuf); if (nullptr != _pBuf) { return; } size_t temp_size = _nSize + sizeof(MemoryBlock); size_t bufSize = temp_size * _nBlockSize; _pBuf = (char*)malloc(bufSize); _pHeader = (MemoryBlock*)_pBuf; if (nullptr != _pHeader) { _pHeader->_bPool = true; _pHeader->_nID = 0; _pHeader->_nRef = 0; _pHeader->_pAlloc = this; _pHeader->_pNext = nullptr; MemoryBlock* pTemp1 = _pHeader; for (size_t n = 1; n < _nBlockSize; n++) { MemoryBlock* pTemp2 = (MemoryBlock*)(_pBuf + (n * temp_size)); pTemp2->_bPool = true; pTemp2->_nID = n; pTemp2->_nRef = 0; pTemp2->_pAlloc = this; pTemp2->_pNext = nullptr; pTemp1->_pNext = pTemp2; pTemp1 = pTemp2; } } }
void* MemoryAlloc::allocMem(size_t nSize) { std::lock_guard<std::mutex> lock(_mutex); if (nullptr == _pBuf) { initMemory(); } MemoryBlock* pReturn = nullptr; if (nullptr == _pHeader) { pReturn = (MemoryBlock*)malloc(nSize+sizeof(MemoryBlock)); if (nullptr != pReturn) { pReturn->_bPool = false; pReturn->_nID = -1; pReturn->_nRef = 1; pReturn->_pAlloc = this; pReturn->_pNext = nullptr; } } else { pReturn = _pHeader; _pHeader = _pHeader->_pNext; assert(0 == pReturn->_nRef); pReturn->_nRef = 1; } if (nullptr != pReturn) { xPrintf("NEW - allocMem:%p,id=%d,size=%d\n", pReturn, pReturn->_nID, nSize); } return ((char*)pReturn + sizeof(MemoryBlock)); }
void MemoryAlloc::freeMem(void* p) { MemoryBlock* pBlock = (MemoryBlock*)((char*)p - sizeof(MemoryBlock)); assert(1 == pBlock->_nRef); if (--pBlock->_nRef != 0) { return; } if (pBlock->_bPool) { std::lock_guard<std::mutex> lock(_mutex); pBlock->_pNext = _pHeader; _pHeader = pBlock; } else { free(pBlock); } }
|
4.内存块类MemoryBlock
4.1 MemoryBlock.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
#ifndef Memory_Block_h #define Memory_Block_h
class MemoryAlloc;
#include<stdlib.h> #include<assert.h> #include<mutex>
#ifdef _DEBUG #include<stdio.h> #define xPrintf(...) printf(__VA_ARGS__) #else #define xPrintf(...) #endif class MemoryBlock { public: int _nID; int _nRef; MemoryAlloc* _pAlloc; MemoryBlock* _pNext; bool _bPool;
private:
};
#endif
|
4.2 MemoryBlock.cpp
5.内存管理工具类MemoryMgr
5.1 MemoryMgr.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
#ifndef Memory_Mgr_h #define Memory_Mgr_h
#define MAX_MEMORY_SIZE 128
#include"MemoryAlloc.h"
class MemoryMgr { public: static MemoryMgr* Instance(); void* allocMem(size_t nSize); void freeMem(void* p); void addRef(void* p);
private: MemoryMgr(); virtual ~MemoryMgr(); void init_szAlloc(int begin,int end,MemoryAlloc* pMem);
private: MemoryAlloc* _szAlloc[MAX_MEMORY_SIZE + 1]; MemoryAlloc _mem64; MemoryAlloc _mem128; };
#endif
|
5.2 MemoryMgr.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| #include"MemoryMgr.h"
MemoryMgr::MemoryMgr() { _mem64.setInit(64, 10); init_szAlloc(0, 64, &_mem64); _mem128.setInit(128, 10); init_szAlloc(65, 128, &_mem128); }
MemoryMgr::~MemoryMgr() { }
void MemoryMgr::init_szAlloc(int begin, int end, MemoryAlloc* pMem) { for (int i = begin; i <= end; i++) { _szAlloc[i] = pMem; } }
MemoryMgr* MemoryMgr::Instance() { static MemoryMgr myMemoryMgr; return &myMemoryMgr; }
void* MemoryMgr::allocMem(size_t nSize) { if (nSize <= MAX_MEMORY_SIZE) { return _szAlloc[nSize]->allocMem(nSize); } else { MemoryBlock* pReturn = (MemoryBlock*)malloc(nSize + sizeof(MemoryBlock)); if (nullptr != pReturn) { pReturn->_bPool = false; pReturn->_nID = -1; pReturn->_nRef = 1; pReturn->_pAlloc = nullptr; pReturn->_pNext = nullptr; xPrintf("NEW - allocMem:%p,id=%d,size=%d\n",pReturn,pReturn->_nID,nSize); } return ((char*)pReturn + sizeof(MemoryBlock)); } }
void MemoryMgr::freeMem(void* p) { MemoryBlock* pBlock = (MemoryBlock*)((char*)p - sizeof(MemoryBlock)); xPrintf("DELETE - allocMem:%p,id=%d\n", pBlock, pBlock->_nID); if (pBlock->_bPool == true) { pBlock->_pAlloc->freeMem(p); } else { if (--pBlock->_nRef == 0) { free(pBlock); } } }
void MemoryMgr::addRef(void* p) { MemoryBlock* pBlock = (MemoryBlock*)((char*)p - sizeof(MemoryBlock)); ++pBlock->_nRef; }
|
6.main
文件
6.1 main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include<stdio.h> #include<stdlib.h> #include"Alloctor.h"
#ifdef _DEBUG #endif
int main() { char* data2 = new char; delete data2;
char* data1 = new char[129]; delete[] data1;
char* data3 = new char[65]; delete[] data3;
printf("--------------------------\n"); char* data[15]; for (size_t i = 0; i < 12; i++) { data[i] = new char[64]; delete[] data[i]; }
return 0; }
|
七、小结
- 在申请与释放内存时,返回给用户和用户传进来的都是可用内存的地址,并不是内存头的地址。我们需要对地址进行偏移,从而返回/接收正确的地址。具体为可用内存地址向前偏移一个内存头大小即为内存头地址;内存头地址向后偏移一个内存头大小即为可用内存地址。
- 内存池初始化时,申请总地址大小为:(可用地址大小+内存头大小) * 内存单元数量
- 内存池外申请的内存,不会在内存池析构函数内被释放,需要手动释放。(不过一般触发析构函数的时候,也不用手动释放了)
- 在这次的项目中,我对地址、内存等有了更深刻的理解,同时也能熟练使用VS的调试功能。希望未来能有更大的发展。