C++设计一个简单内存池的全过程

 更新时间:2021年09月20日 09:58:18   作者:午饭要阳光  
利用C/C++开发大型应用程序中,内存的管理与分配是一个需要认真考虑的部分,下面这篇文章主要给大家介绍了关于C++设计一个简单内存池的全过程,需要的朋友可以参考下

什么是内存池???

通常我们用new或malloc来分配内存的话,由于申请的大小不确定,所以当频繁的使用时会造成内存碎片和效率的降低。为了克服这种问题我们提出了内存池的概念。内存池是一种内存分配方式。内存池的优点就是可以有效的减少内存碎片化,分配内存更快速,减少内存泄漏等优点。

内存池是在真正使用内存之前,先申请分配一个大的内存块留作备用。当真正需要使用内存的时候,就从内存池中分配一块内存使用,当使这块用完了之后再还给内存池。若是内存块不够了就向内存再申请一块大的内存块。

可以看出这样做有两个好处:

  1、由于向内存申请的内存块都是比较大的,所以能够降低外碎片问题。

  2、一次性向内存申请一块大的内存慢慢使用,避免了频繁的向内存请求内存操作,提高内存分配的效率。

内存碎片化:

造成堆利用率很低的一个主要原因就是内存碎片化。如果有未使用的存储器,但是这块存储器不能用来满足分配的请求,这时候就会产生内存碎片化问题。内存碎片化分为内部碎片和外部碎片。

内碎片:

内部碎片是指一个已分配的块比有效载荷大时发生的。(举个栗子:假设以前分配了10个大小的字节,现在只用了5个字节,则剩下的5个字节就会内碎片)。内部碎片的大小就是已经分配的块的大小和他们的有效载荷之差的和。因此内部碎片取决于以前请求内存的模式和分配器实现的模式。

外碎片:   外部碎片就是当空闲的存储器的和计起来足够满足一个分配请求,但是没有一个单独的空闲块足够大可以处理这个请求。外部碎片取决于以前的请求内存的模式和分配器的实现模式,还取决于于将来的内存请求模式。所以外部碎片难以量化。

下面介绍一种简单的内存池,它是针对于某种对象实现的。 我们可以用一个链表实现这个内存池,链表上的每个结点都是一个对象池,如果我们需要申请空间的话,直接去内存池里面申请空间,当用完之后再还给内存池。

内存池的设计主要包含三步:

1、初始化

在创建内存池的时候为内存池分配了一块很大的内存,便于以后的使用。

2、分配内存

当需要内存的时候就去内存池里面分配内存。

3、回收内存

当从内存池里面分配来的内存使用完毕之后,需要将这块内存还给内存池。

设计上面这个内存池最重要的问题就是如何重复利用释放回来的内存,让利用率达到最高???

但是如果当对象的大小小于对象指针的时候,也就是一个对象的空间存不下一个指针的大小,这时候就不可避免的产生内碎片。   例如:为T类型对象开辟对象池,sizeof(T)<sizeof(T*),这时候我们就要为一个T类型对象申请sizeof(T*)大小的内存。

代码实现:
#pragma once
#include<iostream>
using namespace std;
//用链表来实现内存池,每一个结点都挂有一块内存
template<typename T>
class ObjectPool
{
       struct BlockNode         //每一个结点类型
       {
              void* _memory;        //指向一块已经分配的内存
              BlockNode * _next;    //指向下一个结点
              size_t _objNum;       //记录这块内存中对象的个数
              BlockNode(size_t objNum)
                     :_objNum(objNum)
                     , _next(NULL)
              {
                     _memory = malloc(_objNum*_itemSize);
              }
              ~BlockNode()
              {
                     free(_memory);
                     _memory = NULL;
                     _next = NULL;
                     _objNum = 0;
              }
       };
protected:
       size_t _countIn;      //当前结点的在用的计数
       BlockNode* _frist;    //指向链表的头
       BlockNode* _last;     //指向链表的尾
       size_t _maxNum;        //记录内存块最大的容量
       static size_t _itemSize;   //单个对象的大小
       T* _lastDelete;        //指向最新释放的那个对象的空间
public:
       ObjectPool(size_t initNum = 32, size_t maxNum = 100000)  //默认最开始内存块有32个对象,一个内存块最大有maxNum个对象
              :_countIn(0)
              , _maxNum(maxNum)
              , _lastDelete(NULL)
       {
              _frist = _last =new BlockNode(initNum);   //先开辟一个结点,这个结点里面的内存块能够存放initNum个对象
       }
       ~ObjectPool()
       {
              Destory();
       }
       T* New()                   //分配内存
       {
              if (_lastDelete)         //先到释放已经用完并且换回来的内存中去找
              {
                     T* object = _lastDelete;
                     _lastDelete = *((T**)_lastDelete);  //将_lastDelete转换成T**,*引用再取出来T*,也就是取出前T*类型大小的单元
                     return new(object) T();        //把这块内存用从定位new初始化一下
              }
              //判断还有没有已经分配的内存且还未使用,如果没有内存的话就要再分配内存
              if (_countIn >= _last->_objNum)     //大于等于表示没有了,这时候就要分配内存了
              {
                     size_t size =2*_countIn;
                     if (size > _maxNum)            //块的最大大小不能超过maxNum,如果没超过就以二倍增长
                           size = _maxNum;
                     _last->_next = new BlockNode(size);
                     _last = _last->_next;
                     _countIn = 0;
              }
              //还有已经分配好的未被使用的内存
              T* object =(T*)((char*)_last->_memory + _countIn*_itemSize);
              _countIn++;
              return new(object) T();        //将这块空间用重定位new初始化一下
       }
       void Destory()
       {
              BlockNode *cur = _frist;
              while (cur)
              {
                     BlockNode* del = cur;
                     cur = cur->_next;
                     delete del;            //会自动调用~BlockNode()
              }
              _frist = _last = NULL;
       }
       void Delete(T* object)          //释放内存
       {
              if (object)
              {
                     object->~T();       
                     *((T**)object) = _lastDelete;      //将_lastDelete里面保存的地址存到tmp指向空间的前T*大小的空间里面
                     _lastDelete = object;
              }
       }
protected:
       static size_t GetItemSize()
       {
              if (sizeof(T)>sizeof(T*))
              {
                     return sizeof(T);
              }
              else
              {
                     return sizeof(T*);
              }
       }
};
template<typename T>
size_t ObjectPool<T>::_itemSize =ObjectPool<T>::GetItemSize();          //类外初始化静态变量_itemSize

总结

到此这篇关于C++设计一个简单内存池的文章就介绍到这了,更多相关C++设计内存池内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++ LeetCode1781题解所有子字符串美丽值之和

    C++ LeetCode1781题解所有子字符串美丽值之和

    这篇文章主要为大家介绍了C++ LeetCode1781题解所有子字符串美丽值之和,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • C++11右值引用和转发型引用教程详解

    C++11右值引用和转发型引用教程详解

    这篇文章主要介绍了C++11右值引用和转发型引用教程详解,需要的朋友可以参考下
    2018-03-03
  • C++实现彩色飞机大战

    C++实现彩色飞机大战

    这篇文章主要为大家详细介绍了C++实现彩色飞机大战,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-10-10
  • 嵌入式QT移植的实现

    嵌入式QT移植的实现

    本文主要介绍了嵌入式QT移植的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • C语言switch 语句的用法详解

    C语言switch 语句的用法详解

    本文主要介绍C 语言中的switch 语句,这里提供示例代码和详细介绍,希望能帮助学习C语言的小伙伴
    2016-07-07
  • 关于C++ string和c类型字符数组的对比

    关于C++ string和c类型字符数组的对比

    下面小编就为大家带来一篇关于C++ string和c类型字符数组的对比。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-07-07
  • C++连接mysql的方法(直接调用C-API)

    C++连接mysql的方法(直接调用C-API)

    首先安装mysql,点完全安装,才能在在安装目录include找到相应的头文件,注意,是完全安装,需要的朋友可以参考下
    2017-06-06
  • C++数据结构红黑树全面分析

    C++数据结构红黑树全面分析

    今天的这一篇博客,我要跟大家介绍二叉搜索树中的另一颗树——红黑树,它主要是通过控制颜色来控制自身的平衡,但它的平衡没有AVL树的平衡那么严格
    2022-02-02
  • C语言结构体字节对齐的实现深入分析

    C语言结构体字节对齐的实现深入分析

    这篇文章主要介绍了C语言结构体字节对齐的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-10-10
  • C语言实现维吉尼亚密码的示例代码

    C语言实现维吉尼亚密码的示例代码

    维吉尼亚密码(又译维热纳尔密码)是使用一系列凯撒密码组成密码字母表的加密算法,属于多表密码的一种简单形式。本文将用C语言实现维吉尼亚密码,需要的可以参考一下
    2022-11-11

最新评论