内存池的实现

来源:互联网 发布:mac os最新版本下载 编辑:程序博客网 时间:2024/06/05 02:05

根据C++STL中的空间配置器,实现一个轻量级的内存池,由于空间配置器虽然解决了外部碎片的问题,提高了效率,但它的缺陷在于若使用二级空间配置器,它不会主动释放已经空闲的内存块,还给操作系统,而是将自己申请的内存块全部挂在自由链表上,自己不用,别的进程也不可以用,造成极大的内存空间的浪费,很可能导致很多别的进程无内存可用的情况。而轻量级的内存池不仅解决外部碎片问题,并且解决释放内存问题,它使用多少用多少,即在堆上申请的内存基本都在使用,不会大量闲置,并且当所有内存块都已不在使用时,在直接释放还给操作系统。

实现结构如下:


在以上结构实现中,首先要考虑以下几点:
1、链表节点memory指向的内存块大小以2倍增长(即第一个节点若挂3块内存块,第二个节点则挂6块);
2、在实现中,若第一个节点中挂的内存块有空闲,则不会开辟链表第二个节点并相应的开辟新内存,是直接使用前一个节点空闲的内存块,以致不会造成一个程序对内存的大量占用却不使用的情况,所以由此也可得出若要开辟一个新节点,说明前面节点管理的内存都已被使用;
3、在不断开辟申请使用内存过程中,前面也必定有使用完成之后已释放的内存块,所以在申请内存时先要检测先前是否有释放的内存块,若有,则使用已释放的内存块。
代码实现:

#pragma onceusing namespace std;#include <string>template <class T>class ObjectPool{struct Node{void* memory;   //指向挂着的内存块size_t n;     //挂的内存块个数Node* next;    //指向下一个内存块节点头Node(size_t nobjs):n(nobjs),next(NULL){memory=::operator new(n*GetSize());}~Node(){::operator delete(memory);memory=NULL;n=0;next=NULL;}};public:ObjectPool(size_t nobjs=16,size_t maxNobjs=1024):_initNobjs(nobjs),_maxNobjs(maxNobjs),_useInCount(0),_lastDelete(NULL){_head=new Node(_initNobjs);_tail=_head;}~ObjectPool(){Node* cur=_head;while(cur){Node* del=cur;cur=cur->next;delete del;del=NULL;}_head=_tail=NULL;_lastDelete=NULL;_initNobjs=_maxNobjs=_useInCount=0;}public://封装一层接口template <class Val>T* New(const Val& val){void* obj=Allocate();return new(obj)T(val);//new定位表达式初始化}void Delete(T* ptr){if(ptr){ptr->~T();//若为自定义类型,先调用其类型的析构函数Deallocate(ptr);  //释放该内存}}protected:void* Allocate()   //申请资源{//1.先查找内存是否有释放的内存块if(_lastDelete){void* ptr=_lastDelete;_lastDelete=*((T**)_lastDelete);return ptr;}//2.没有释放开辟新内存if(_useInCount>=_tail->n)AllocNewNode();//返回内存块void* ptr=(char*)(_tail->memory)+_useInCount*GetSize();++_useInCount;return ptr;}void Deallocate(void* ptr){//链表的隐形头插*((T**)ptr)=_lastDelete; //取当前释放的内存块内容的前(T*)个字节,存放上一次释放的内存块的地址_lastDelete=(T*)ptr;  //标记释放的内存块的位置}void AllocNewNode()  //申请新节点{size_t n=_tail->n*2;//开辟内存块数为上一次2倍if(n>=_maxNobjs)n=_maxNobjs;  Node* node=new Node(n);//链表尾插_tail->next=node;_tail=node;_useInCount=0;//更新}inline static size_t GetSize()//32、64位系统兼容,指针大小分别为4、8字节,使一个内存块至少存放下一个指针大小{return sizeof(T)>sizeof(T*)?sizeof(T):sizeof(T*);}protected:size_t _initNobjs;    //初始化开辟内存块个数size_t _maxNobjs;    //开辟内存块个数最大值Node* _head;        //管理内存块链表的头Node* _tail;        //管理内存块链表的尾size_t _useInCount;  //已用的内存块个数T* _lastDelete;    //当前所有释放的内存块的链表头};void Test(){ObjectPool<int> obj(3,1024);int* p1;int* p2;int* p3;int* p4;int* p5;p1=obj.New(1);p2=obj.New(2);p3=obj.New(3);p4=obj.New(4);obj.Delete(p2);p5=obj.New(5);obj.Delete(p4);obj.Delete(p3);obj.Delete(p1);int* p6=obj.New(6);ObjectPool<string> pool1;string* p7 = pool1.New("测试");pool1.Delete(p7);}
在以上代码具体实现中,

1.在构造链表节点时,即构造了节点,又同时开辟了指定大小的新内存,初始化了指针memory,挂起了内存块。
2.在对使用完的内存块释放调用Deallocate()函数时,运用了链表的隐式头插,如图:


即在这次释放的内存块中的sizeof(T*)个字节存放上一次释放的内存块首地址,并更新_lastDelete,使它指向释放内存块的链表头,以此也实现了一物二用的效果。所以在申请资源调用Allocate()函数时,检查是否有释放的空闲块时,若有则实现隐形头删,返回_lastDelete现指向的内存地址,并更新它的值使它指向当前内存块下一个。
3.为实现第2点提出的一物二用,即每个内存块大小则至少要满足存储sizeof(T*)大小(一个指针大小),为了满足系统的兼容性,则必须保证每次申请的内存块大小>=sizeof(T*),若小于,则自动提升为sizeof(T*)。

0 0