简单实现一个固定大小的内存池
来源:互联网 发布:网吧电脑保护软件 编辑:程序博客网 时间:2024/06/05 18:53
1.内存池的引入
(1)内存池顾名思义就是存放内存的池子,直接分配一大块内存存起来,然后慢慢地切出来使用,用完了再还回到池子,后续还能再利用。
(2)如果我们需要申请一块空间,我们一般都会动态开辟一块空间(new或malloc),它们两个的共同点就是在堆上去申请空间,当然也是有一定限制的。如果我们频繁的去申请释放空间,就会造成许多的内存碎片,内存的浪费也就产生了。如果我们可以将自己需要大小的空间保存起来(不交给操作系统管理),下次需要的时候,从自己保存的内存池子里去取,这样不仅效率高,而且还不会造成太大的浪费。
(3)内存池的设计主要是为了应对一些特殊场景,比如说:操作系统频繁的分配和释放小块内存,不仅影响分配效率而且易造成外碎片问题,在某种程度上内存池就能很好地解决这类问题,并且内存池还可以避免内存泄露等问题,既然内存池有这么多的优点,那我们该如何去设计一个内存池呢?
源自韦易笑作者的一篇博文中,他在实现一个固定大小的内存分配器的过程中是这样思考的:
即实现一个 FreeList,每个 FreeList 用于分配固定大小的内存块,比如用于分配 32字节对象的固定内存分配器,之类的。每个固定内存分配器里面有两个链表,OpenList 用于存储未分配的空闲对象,CloseList用于存储已分配的内存对象,那么所谓的分配就是从 OpenList 中取出一个对象放到 CloseList 里并且返回给用户,释放又是从 CloseList 移回到 OpenList。分配时如果不够,那么就需要增长 OpenList:申请一个大一点的内存块,切割成比如 64 个相同大小的对象添加到 OpenList中。这个固定内存分配器回收的时候,统一把先前向系统申请的内存块全部还给系统。
(3)有关内碎片和外碎片的理解
2.内存池的实现
1>这个内存池的思路很简单:每次分配一个node,一个node相当于一个小的对象池,这个小池子用完了,再分配一个尺寸大一倍的node,这些node是以链表的方式链接起来的。(每一个节点管理一块内存,设定各个内存块存储对象的多少按2倍增长)
如图内存池实现机制:
2>内存是如何分配的?
这里分三种情况:
@优先使用以前释放的空间;
@如果没有先前释放的空间了,且当前节点还剩下未使用的内存就去内存块中申请;
@当前节点下的内存不足以我们用来分配,就开辟新的节点以获得新节点下维护的内存对象个数.
情况一如下图:
上面还有个小问题要注意:
情况二如图:
情况三:
这种情况就是需要开辟新的节点以获得新节点下维护的内存对象个数,需要注意两点就是,新开辟出来的节点在用计数要置为0;链表尾指针last要变动(刚开始一个节点的时候_first和_last指向相同),_last=_last->_next;
3.下面是代码实现:
#pragma once#include<vector>#include<string>#include<cstdlib>#include<iostream>using namespace std;//用链表实现内存池,每个节点下面挂一块内存template<class T>class ObjPool{struct BlockNode{void* _memory; //内存块BlockNode* _next; //指向下一个节点的指针size_t _ObjNum; //内存对象的个数BlockNode(size_t ObjNum):_ObjNum(ObjNum),_next(NULL){_memory=malloc(_ItemSize*_ObjNum); //单个对象的大小与内存对象的个数的乘积if(_memory==NULL){cout<<"out of memory"<<endl;exit(1);}}~BlockNode(){free(_memory); //_memory是通过malloc在堆上开辟的_memory=NULL;_next=NULL;_ObjNum=0;}};public://固定大小的内存池ObjPool(size_t InitNum=32,size_t MaxNum=100000)//一个节点下面先默认开32字节大小的内存:_CountIn(0),_MaxNum(MaxNum),_LastDelete(NULL){_first=_last=new BlockNode(InitNum);}~ObjPool(){//释放节点下面挂的内存块BlockNode* cur=_first;while(cur){BlockNode* del=cur;cur=cur->_next;delete del; //调用节点的析构函数del=NULL;}_first=_last=NULL;_CountIn=0;}//申请空间T* New(){//1.优先使用以前释放的空间//2.去内存块中申请//3.申请新的节点对象if(_LastDelete){T* obj=_LastDelete;//强转为二级指针再解引用就能正确的取到该指针所指向的内容,解决了32位程序和64为程序的限制_LastDelete=*((T**)_LastDelete);//使用的是还回来的内存,所以没必要再对当前节点在用计数_CountIn++return new(obj)T; //new定位表达式}//所有结点的内存块都没有可以使用的,则重新开辟新的节点if(_CountIn==_last->_ObjNum){size_t NewNodeObjNum=_last->_ObjNum * 2;if(NewNodeObjNum>=_MaxNum)_MaxNum=NewNodeObjNum;_last->_next=new BlockNode(NewNodeObjNum);if(_last->_next==NULL){throw(-1);}_CountIn=0; //新开的节点,当前节点在用计数置为0_last=_last->_next;}//在还没用的内存块下面去分配使用T* obj = (T*)((char*)_last->_memory + _CountIn*_ItemSize);_CountIn++;return new(obj)T;}//使用完小块内存后还给内存池时,Delete先调对象的析构函数,然后将这块内存头插入自由链表中void Delete(T* ptr){if(ptr){ptr->~T();}*(T**)ptr=_LastDelete;_LastDelete=ptr;}static size_t Get_ItemSize(){//BlockNode中存储了void* 的一个指针,所以最低限度要开出一个能存放void*指针大小的对象空间if(sizeof(void*) >= sizeof(T))return sizeof(void*);elsereturn sizeof(T);}protected:size_t _CountIn; //当前节点在用的计数BlockNode* _first; //指向链表的头BlockNode* _last; //指向链表的尾size_t _MaxNum; //节点申请内存块的对象个数static size_t _ItemSize; //单个对象的大小T* _LastDelete; //指向最后释放的空间};template<class T>size_t ObjPool<T>::_ItemSize=ObjPool<T>::Get_ItemSize(); //类外调用静态成员函数
- 简单实现一个固定大小的内存池
- 内存池——实现一个简单的固定大小的内存池
- 一个简单的固定长度内存池的实现
- c++ 固定分配大小的简单内存池
- 单线程固定大小对象的内存池实现
- 单线程不固定大小对象的内存池实现
- 固定大小内存池设计与实现
- 一个对大小固定的内存数据进行操作的简单类
- 实现一个大小固定的矩阵
- 固定大小块的内存池设计
- 一个分配固定大小块的内存池(memory pool) 的例子 FROM POCO
- C语言固定大小的线性表的简单实现
- 内存管理模拟实验之实现一个简单的固定(可变)分区存储管理系统
- 最简单的一种栈 固定大小 [数组实现]
- 一个简单的内存池的实现
- 一个简单的内存池的实现
- 单线程环境中对象大小固定的内存池
- 对象大小固定的单线程内存池设计
- HDU 1394(线段树) Minimum Inversion Number
- C++ 高版本打开低版本无法解析。。printf
- new 对象加括号和不加括号的区别
- 处理大并发之二 对epoll的理解,epoll客户端服务端代码
- mysql 的innoDB和NDB数据库引擎
- 简单实现一个固定大小的内存池
- linux 中常用目录的含义
- 树莓派不小心误删了桌面的回收站,如何找回
- 设计模式-创建型模式:单例模式 Singleton
- Leetcode165. Compare Version Numbers
- css学习之定位
- spring+mybatis报Cannot load JDBC driver class ${xxxxx}'错误
- 安卓中各种对话框
- Git学习