您的位置:首页 > 编程语言 > C语言/C++

内存池——实现一个简单的固定大小的内存池

2016-12-04 21:48 260 查看
最近在STL当中看到了第二级内存分配器,这里有个内存池的内容,在这在知乎上看到了内存池的相关内容,所以萌生了一个想自己写一个简单的内存池的想法。

这种简单的内存池,援引自知乎的:

实现固定内存分配器:

即实现一个 FreeList,每个 FreeList 用于分配固定大小的内存块,比如用于分配 32字节对象的固定内存分配器,之类的。每个固定内存分配器里面有两个链表,OpenList 用于存储未分配的空闲对象,CloseList用于存储已分配的内存对象,那么所谓的分配就是从 OpenList 中取出一个对象放到 CloseList 里并且返回给用户,释放又是从 CloseList 移回到 OpenList。分配时如果不够,那么就需要增长 OpenList:申请一个大一点的内存块,切割成比如 64 个相同大小的对象添加到 OpenList中。这个固定内存分配器回收的时候,统一把先前向系统申请的内存块全部还给系统。

1.探索内存池的使用原因

为什么要使用内存池呢?这个解决方案也算是突出CPP的效率的一个方面了,我们会经常面对类似于高并发的这种情况,即简单理解为经常性的new对象,然后delete对象,经常性的发出分配请求和释放请求给操作系统,势必带来效率的下降,所以考虑到这一点,设计者们设计出了内存池。

内存池简单的理解就是,进行一次性的开出大块空间,当你需要构造对象的时候,拿出来构造,当你需要释放的时候,并不还给操作系统,还给内存池,然后接下来使用直接从内存池当中取出进行操作。

所以,在多次进行分配请求与释放请求的场景下,内存池能够提高效率。

另外,当我们进行释放以后,会产生内碎片的问题,就是对一块整块的内存,会出现中间出现片段,这样,为了提升内存的利用率,我们把这快空间还回内存池,下次可以再使用这块片段,提升了效率,详细看下面的图我相信你能够理解的。

所以,内存池也可以提高内存利用率,解决外碎片的问题。

2.内存池的实现

对下面这种内存池进行实现,下面的内存池进行实现的时候,我们其实就是简单的实现成为一个自由链表的形式,接下来,依靠图示的情况为大家讲解。

其实,与其说我们的内存池是一个内存池,并不如说是一个对象池,因为我们这里的思路的主要核心就是通过对象的构造和析构来维护整个内存池。



#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<cstdlib>

using namespace std;

template<typename T>
class objectpool
{
//首先给出内存池的节点,
struct BlockNode
{
void* _memory;          //对应每个节点所挂的内存块
BlockNode * _next;      //指向下一个节点的指针。
size_t _objNum;         //表示内存对象的个数
//进行内存块的初始化
BlockNode(size_t objnum)
:_memory(NULL)
, _objNum(objnum)
{
_memory = malloc(_itemsize*objnum);
}
//内存块析构
~BlockNode()
{
free(_memory);
_memory = NULL;
_next = NULL;
}
};
public:
//用来初始化对象大小的函数
static size_t IninItemSize();
//固定大小对象池大构造函数
objectpool(size_t initNum = 32, size_t maxNum = 100000)
:_countin(0)
, _Maximum(maxNum)
{
_first = _last = new BlockNode(initNum);
_lastDelete = NULL;
}
//对象池的析构
~objectpool()
{
Destory();
_first = _last = NULL;

////当把所有的内存块进行释放以后,这个时候_lastdelete不允许还链有内存块
//
//if (_lastDelete==NULL)
//  cout << "发生了内存泄漏" << endl;

}
void Destory()
{
//进行销毁内存池的时候,我们这个时候采取的方式就是释放每一个节点挂的memory。
BlockNode* cur = _first;
BlockNode* del = NULL;
while (cur)
{
del = cur;
cur = cur->_next;
delete del;
}

}
T* New()
{
//首先释放以前的内存地址空间
//内存块里面进行申请
//申请新的节点对象
if (_lastDelete)
{
T* obj = _lastDelete;
//强转为T**,然后解引用。这里解决了32位程序和64为程序的限制,T**直接取到了正确的地址内容。
_lastDelete = *((T**)_lastDelete);

//此时从自由链表中拿出节点构造对象,所以不需要对countin++
//_countin++;

return new(obj)T;
}

//当BlockNode满了的时候,这个时候就需要去到下一个BlockNode去构造对象。
if (_countin == _last->_objNum)
{
size_t newNodeNum = _last->_objNum * 2;         /
if (newNodeNum >= _Maximum)
newNodeNum = _Maximum;
_last->_next = new BlockNode(newNodeNum);
if (_last->_next == NULL)
cout << "内存开辟失败" << endl;
_countin = 0;
_last = _last->_next;
}

//在memory上剩余的位置进行构造
T* obj = (T*)((char*)_last->_memory + _countin*_itemsize);
//T* obj = (T*)((T*)_last->_memory + _countin);
_countin++;
return new(obj)T;

}

void  Delete(T* ptr)
{
//先调用析构函数,然后把要析构的对象的内存块返回到内存池当中,也就是头插入自由链表_lastdelete中。
ptr->~T();
if (ptr)
{
*(T**)ptr = _lastDelete;
_lastDelete = ptr;
}

}
private:
size_t _countin;    //记录当前节点使用的计数
BlockNode* _first;
BlockNode* _last;
size_t _Maximum;    //记录最大节点最大的对象数目
static size_t _itemsize ;   //单个对象的大小
T* _lastDelete;         //维护内存池

};
template<class T>
size_t objectpool<T>::IninItemSize()
{
//BlockNode中存储了void* 的一个指针,所以最低限度你要开出来一个能存放void*的指针的大小的对象空间。
if (sizeof(T) <= sizeof(void*))
return sizeof(void*);
else
return sizeof(T);
}

template<class T>
size_t objectpool<T>::_itemsize = objectpool<T>::IninItemSize();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息