万字详解C++内存池:提高内存分配效率的利器(上)

简介: 万字详解C++内存池:提高内存分配效率的利器

内存池(Memory Pool)是一种内存分配方式。通常我们习惯直接使用new、malloc等API申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。

image.png


内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。


一、什么是内存池


1.1池化技术


池化技术是计算机视觉和深度学习领域中常用的一种特征降维方法。它的作用是在保留图像或特征图中最显著信息的同时减少数据量,从而提高计算效率并防止过拟合


其内涵在于:将程序中需要经常使用的核心资源先申请出来,放到一个池内,有程序自管理,这样可以提高资源的利用率,也可以保证本程序占有的资源数量,经常使用的池化技术包括内存池,线程池,和连接池等,其中尤以内存池和线程池使用最多。池化操作通常应用于卷积神经网络(CNN)中,对输入的特征图进行处理。


常见的池化方式有最大池化(Max Pooling)和平均池化(Average Pooling)两种:


最大池化:在每个区域内选择最大值作为输出值。这样可以保留局部区域内的最显著特征,丢弃次要信息。它具有平移不变性,在一定程度上提高了模型对位置变化的鲁棒性。


平均池化:在每个区域内计算平均值作为输出值。这种方式可以对整个区域进行平均,从而得到更加全局的统计信息,适用于一些需要考虑整体趋势而不仅仅关注局部细节的任务。


通过不断地堆叠卷积层和池化层,可以有效地减小输入数据尺寸、降低复杂度,并增强网络对平移、缩放等形变操作的鲁棒性。池化技术在卷积神经网络中被广泛应用,有助于提取更具有抽象特征的表示,进而提高模型的准确性和泛化能力。


1.2内存池


内存池是一种用于管理和分配内存的技术,它通过预先申请一块固定大小的内存空间,并将其划分为多个小块,提供给程序按需分配和释放。下面我会详细解释内存池的工作原理和优势。


工作原理:

  • 初始化阶段:在程序启动时,内存池会申请一块连续的大块内存空间。
  • 分配阶段:当程序需要申请内存时,内存池会从已经分配好的空闲块中找到合适大小的小块来分配给程序。
  • 释放阶段:当程序不再需要某个内存块时,可以将其返回给内存池进行复用。
  • 扩展阶段:如果当前内存池中没有足够的可用空间了,可以根据需要扩展内存池的大小。

优势:

  • 减少碎片化:由于内存池事先划分好了固定大小的小块,避免了频繁的系统调用和堆碎片产生。
  • 提高性能:由于减少了对系统调用和堆管理操作的依赖,提高了程序运行效率。
  • 控制资源消耗:通过限制总共使用的内存量,避免了程序过度申请内存的问题,更好地管理系统资源。

需要注意的是,内存池并不适用于所有场景。在某些情况下,例如对于大块连续内存的分配需求或者动态变化的内存需求,使用内存池可能不太合适。

【文章福利】小编推荐自己的Linux内核技术交流群:【 865977150】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!


1.3为什么需要内存池


内存池是为了提高程序运行效率和减少内存碎片而设计的一种技术。以下是使用内存池的主要原因:

  1. 减少内存分配和释放的开销:传统的动态内存分配(如malloc、free)会涉及到操作系统的系统调用,这样会产生较大的开销。而使用内存池可以预先分配一块连续的内存空间,并在需要时直接从中取出或回收,避免频繁进行系统调用,从而提高了程序性能。
  2. 减少内存碎片:频繁地进行动态内存分配和释放容易导致堆中产生碎片化的空间。而使用内存池可以固定大小的块来管理内存,避免了不同大小的对象交错排布在堆上导致的碎片问题。
  3. 提高缓存命中率:将对象预先分配到连续的块中,可以利用局部性原理提高缓存命中率。相邻对象在物理上也相邻,利于CPU缓存预取等优化。


1)内存碎片问题


内存碎片是指已分配的内存空间中存在的一些零散、不连续的小块未被使用的内存。这会导致内存利用率下降,可能影响程序的性能和稳定性。造成堆利用率很低的一个主要原因就是内存碎片化。如果有未使用的存储器,但是这块存储器不能用来满足分配的请求,这时候就会产生内存碎片化问题。


内存碎片主要分为两种类型:外部碎片和内部碎片。

  1. 外部碎片:即由于已分配内存块的释放而造成的未被充分利用的空闲内存块。这些空闲块虽然总大小足够,但由于彼此之间存在占用的块,无法组合成足够大的连续空闲区域。
  2. 内部碎片:即已分配给进程或应用程序的内存中,实际使用但没有被完全填满的部分。例如,某个进程申请了100个字节的内存,但只实际使用了80个字节,那么剩下的20个字节就构成了一个内部碎片。


解决内存碎片问题可以采取以下几种方法:

  1. 碎片整理(Defragmentation):通过将已分配和未分配的内存重新组织排列来减少外部碎片。这通常需要暂停程序运行并进行较大规模数据搬移操作。
  2. 分区策略优化:根据程序的内存使用特点,合理划分内存分区,减少外部碎片产生的可能性。例如采用动态分区或伙伴系统等算法。
  3. 内存池管理:提前申请一块较大的内存空间,并自行管理内部碎片和对象的分配与释放,减少频繁的内存申请和释放操作。
  4. 垃圾回收机制:对于某些编程语言如Java、Python等,利用垃圾回收器自动清理不再使用的对象,可以有效减少内部碎片。


申请效率问题


例如:我们上学家里给生活费一样,假设一学期的生活费是6000块。

方式1:开学时6000块直接给你,自己保管,自己分配如何花。

方式2:每次要花钱时,联系父母,父母转钱。

同样是6000块钱,第一种方式的效率肯定更高,因为第二种方式跟父母的沟通交互成本太高了。

同样的道理,程序就像是上学的我们,操作系统就像父母,频繁申请内存的场景下,每次需要内存,都像系统申请效率必然有影响。


1.4内存池的演变

  • 最简单的内存分配器
    做一个链表指向空闲内存,分配就是取出一块来,改写链表,返回,释放就是放回到链表里面,并做好归并。注意做好标记和保护,避免二次释放,还可以花点力气在如何查找最适合大小的内存快的搜索上,减少内存碎片,有空你了还可以把链表换成伙伴算法。
    优点: 实现简单
    缺点: 分配时搜索合适的内存块效率低,释放回归内存后归并消耗大,实际中不实用。
  • 定长内存分配器
    即实现一个 FreeList,每个 FreeList 用于分配固定大小的内存块,比如用于分配 32字节对象的固定内存分配器,之类的。每个固定内存分配器里面有两个链表,OpenList 用于存储未分配的空闲对象,CloseList用于存储已分配的内存对象,那么所谓的分配就是从 OpenList 中取出一个对象放到 CloseList 里并且返回给用户,释放又是从 CloseList 移回到 OpenList。分配时如果不够,那么就需要增长 OpenList:申请一个大一点的内存块,切割成比如 64 个相同大小的对象添加到 OpenList中。这个固定内存分配器回收的时候,统一把先前向系统申请的内存块全部还给系统。
    优点: 简单粗暴,分配和释放的效率高,解决实际中特定场景下的问题有效。
    缺点: 功能单一,只能解决定长的内存需求,另外占着内存没有释放。

1.5哈希映射的FreeList池


在定长分配器的基础上,按照不同对象大小(8,16,32,64,128,256,512,1k…64K),构造十多个固定内存分配器,分配内存时根据要申请内存大小进行对齐然后查H表,决定到底由哪个分配器负责,分配后要在内存头部的 header 处写上 cookie,表示由该块内存哪一个分配器分配的,这样释放时候你才能正确归还。如果大于64K,则直接用系统的 malloc作为分配,如此以浪费内存为代价你得到了一个分配时间近似O(1)的内存分配器。这种内存池的缺点是假设某个 FreeList 如果高峰期占用了大量内存即使后面不用,也无法支援到其他内存不够的 FreeList,达不到分配均衡的效果。


优点:这个本质是定长内存池的改进,分配和释放的效率高。可以解决一定长度内的问题。


缺点:存在内碎片的问题,且将一块大内存切小以后,申请大内存无法使用。多线程并发场景下,锁竞争激烈,效率降低。


范例:sgi stl 六大组件中的空间配置器就是这种设计实现的。


二、内存池设计


在设计一个内存池时,首先要对内存池的存储数据部分的构建做一个大概的规划。


因为是动态申请内存,没有办法预计将来正在运行的程序究竟会需要多大的内存空间,因此在内存池的设计上要预留空间,未来防止盲目的使用过大空间,采用的方法就是用多个内存块组成一个内存池,第一次分配的时时,先申请一个内存块,当程序不够用的时候,再向系统申请一块内存,然后将新内存与前面已获得的内存块组织成链表,以后一旦发现内存不够,那么就如此继续申请后续内存块,从而组成一个多内存块的内存池。内存池中内存块的长度可以相等,也可以不相等。


因为 STL容器中的元素大小都是相等的,故在此将内存块的存储区域划分为一个一个大小为 nunitsize 的存储单位。


程序一旦由系统获得内存池所需内存之后,在程序把内存池归还系统之前,系统绝不会再对这些内存进行任何管理工作,要么由程序委派给其它部件,在 STL中,由于空间配置器的存在,那么内存池的管理工作就是由它来执行。


首先要管理的就是内存块的信息表:

struct memoryblock
{
int nsize;  // 该内存的大小
int nfree;  //该内存还有多少可分配的单位
int nfirst;  // 当前可分配的第一个单位序号
memoryblock *pnext; // 指向下一个内存块
char adata[1]  //用于标记分配单元开始的位置
}


本例将内存块的信息表 Memoryblock 安排在前面,即在一个内存块中,前面的部分为内存块信息表,紧接着表的便是内存块的数据区域,未来能够获得数据存储去的首地址,在信息表 memoryblock 的最后位置安排了 域 adata[1],这里 adata 就是数据存储去的首地址。


对于内存块中空闲存储单位的管理就是内存池的主要管理工作。本例在对这些空间存储区域进行管理时,采用了一个小技巧,即把它们组织成链表,而链表中那些用于指向下一节点的 “指针” 就保存在各个空闲单位的前两个字节,从而免去了为这些“ 指针” 按照存储空间消耗。 至于空闲存储单位链表的头指针则保存于 表 memoryblock 的nfirst 域。需要注意的是,这里所说的空闲存储单位链表各个节点的指针实在内存块初始化时为这些节点安排的序号而不是真正的地址,当然这个序号可以看成是内存块中的内部地址。


链表头memorypool的信息如下:

struct memorypool
{
    int ninitsize;///首块长度
    int ngrowsize;///后续块长度
    int nunitsize;/// 定义存储单位大小
    memoryblock *pblock; //指向内存块链表的指针
}


从 adata 开始的区域为内存块用于存储数据元素的空间,这里将它称为数据空间。该数据空间以数据的大小 nunitsize 划分为存储单位,每个存储单位可以存储一个数据。为了对尚未被分配使用的空闲单位进行识别,需要对他们编织序号。在内存块初始化时要编制序号,当内存块被被释放回内存池时也要为他们编制序号,总之内存池管理系统是按照序号来识别和管理空闲存储单位的,至于那些被应用程序占用的单位,则其序号自然消失和失效,直至被程序释放回内存时再由回收函数重新为其编号。


显然对于 memoryblock 构造函数来讲,除了对 memoryblock 的各个域进行初始化之外,为空闲单位编制序号,即进行空闲单位存储链表的构建就是它的任务。


综上, memoryblock 的构造函数如下:

memoryblock(int nunitsize,int nunitamount):nsize(nunitsize * nunitamount),nfree(nunitamount-1),nfirst(1),pnext(NULL)
    // nunitsize 存储单位的大小 即 sizeof(T) nunitamount 内存块规模
    // nfree 空闲存储单位为 nunitamount-1 nfirst 初值为1 pnext指向下一个内存块的指针为NULL
    {
        //为空闲区编号
      char *ppdata=adata;
      cout<<"存储区首指针 ppdate ="<<(int*)ppdata<<endl;
      for(int i=1;i<nunitamount;i++)
      {
          //当前存储单位存储下一个可分配单位的序号
          (*(unsigned short *)ppdata)=i;
          cout<<"下一个可分配单位序号: "<<(int)*ppdata<<endl;
          //移动指针使之指向下一个单位
          ppdata+=nunitsize;
      }
      cout<<"-------------调用内存块的构造函数-------------------"<<endl;
    }

因为构造函数的调用发生在用户第一次向内存池请求数据存储单位时,故 存储区域中的第一个存储单位可视为已被分配而不是空闲单位,故需要真正编制序号的是 nunitamount-1个,并且那个用于存储空闲单位链表头部序号的nfirst 也被初始化为1

按照面向对象的方法,用于向系统申请内存的函数 operator new() 也因该重载于类 memoryblock 中,因为c++ 提供的全局 operator new 不再适合本例。

void *operator new(size_t,int nunitsize,int nunitamount)
      {
          cout<<"分配内存并创建memoryblock 对象"<<endl;
          return ::operator new(sizeof(memoryblock)+nunitsize*nunitamount);// 内存空间的长度为 memoryblock 对象加上 存储空间长度总和
      }

其析构函数为:

void operator delete(void *pblock)  //重载 delete 作为内存块的析构函数
      {
          ::operator delete(pblock);
          cout<<"---------------调用内存块的析构函数----------------"<<endl;
      }

memorypool 的设计 相比于 memoryblock 的设计就相对简单一点 主要是对其域初始化其代码如下:

memorypool(int _ngrowsize=10,int _ninitsize=3)
{
    cout<<"-----------------调用内存池的构造函数---------------"<<endl;
    ninitsize=_ninitsize;  // 首块长度
    ngrowsize=_ngrowsize;   //后续块长度
    pblock=NULL; // 链表指针置为 null
    nunitsize=sizeof(T);  // 定义存储单位大小
    if(sizeof(T)>4)///调整存储单位的大小
        nunitsize=(sizeof(T)+(MEMPOOL_ALIGNMENT-1)) & ~(MEMPOOL_ALIGNMENT-1);// 返回值为8的倍数 所谓的内存对齐
    else if(sizeof(T)<2)
        nunitsize=2;
    else
        nunitsize=4;
}

memorypool 的析构函数如下:

~memorypool()
{
    memoryblock<T>*pmyblock=pblock;
    while(pmyblock!=NULL)
    {
        pmyblock=pmyblock->pnext;
        delete(pmyblock);
    }
    cout<<"--------------------调用内存池的析构函数-------------------"<<endl;
}

在 memorypool中 主要操作就是用于向内存池请求存储单位的函数 allocate() ,这也是内存池向用户提供的主要服务。从程序的执行任务看,该函数的工作就是遍历内存块,找到 nfree 大于0 ,即有空闲单位的内存块,并从其上分配存储单位。然后将内存块 memoryblock 表中域 nfree 减一,并修改空闲单位链表头指针 nfirst 的值。

其代码如下:

void *allocate(size_t num)
{
    for(int i=0;i<num;++i)
    {
    if(NULL==pblock)
    {
        ///创建首内存块
        pblock=(memoryblock<T>*)new (nunitsize,ninitsize)
        memoryblock<T>(nunitsize,ninitsize);
        return (void*)pblock->adata;///返回内存块中数据元素存储区指针
    }
    ///为内存寻找符合条件的内存块
    memoryblock<T>*pmyblock=pblock;
    while(pmyblock!=NULL&&0==pmyblock->nfree)
        pmyblock=pmyblock->pnext;
    if(pmyblock!=NULL)
    {
        cout<<"找到内存空间 first= "<<pmyblock->nfirst<<endl;
        ///找到后进行内存分配
        char *pfree=pmyblock->adata+pmyblock->nfirst*nunitsize;
        pmyblock->nfirst=*((unsigned short*)pfree);
        pmyblock->nfree--;
        ///返回找到的存储单位指针
        return (void*)pfree;
    }
    else
    {
        ///没有找到 说明当前内存块已用完
        if(0==ngrowsize)return NULL;
        cout<<" 否则分配新内存块"<<endl;
        //分配一个后续内存块
        pmyblock=(memoryblock<T>*)new(nunitsize,ngrowsize)
        memoryblock<T>(nunitsize,ngrowsize);
        if(NULL==pmyblock)
            return NULL;///失败
        pmyblock->pnext=pblock;
        pblock=pmyblock;
        //返回新内存的的存储区指针
        return (void*)pmyblock->adata;
    }
}
}

memorypool 的另一个重要函数就是用于释放内存的 free() ,该函数根据 pfree 的值,找到他所在的内存块,然后将他的序号 nfirst 的值(因为他绝对空闲) ,在pfree 的头两个字节写入原来nfirst的值,然后判断 该 块的是否全为 free 方法是检测 nfree*nunitsize==nsize。

若是,则向系统释放内存,若不是,则将该 block放到链表的头部,因为该block上含有空闲的内存单元,这样可以减少分配时遍历链表所消耗的时间。

void free(void *pfree)
{
    //找到p所在的块
    cout<<"释放存储单位内存空间"<<endl;
    memoryblock<T>*pmyblock=pblock;
    memoryblock<T>*preblock=NULL;
    while(pmyblock!=NULL&&(pblock->adata>pfree||pmyblock->adata+pmyblock->nsize))
    {
        preblock=pmyblock;
        pmyblock=pmyblock->pnext;
    }
    //该内存块在被内存池pmyblock 所指向的内存块中
    if(NULL!=pmyblock)
    {
        //1 修改数组链表
        *((unsigned short*)pfree)=pmyblock->nfirst;
        pmyblock->nfirst=(unsigned short)((unsigned long)pfree-(unsigned long)pmyblock->adata)/nunitsize;
        pmyblock->nfree++;
        // 2 判断是否需要向系统释放内存
        if(pmyblock->nsize==pmyblock->nfree*nunitsize)
        {
            //在链表中删除
            delete(pmyblock);
        }
        else
        {
            //将该block 插入队首
            preblock=pmyblock->pnext;
            pmyblock->pnext=pblock;
            pblock=pmyblock;
        }
    }
}

将以上代码组装,并自己设计累 user 来测试这个内存池:

#include<iostream>
#define MEMPOOL_ALIGNMENT 8  // 对齐长度
using namespace std;
///内存块及其信息表 memory block-----------------------
template<typename T>
struct memoryblock
{
    int nsize;//该内存块的大小
    int nfree;// 该内存还有多少可分配单位
    int nfirst;//当前可分配的第一个单位序号
    memoryblock *pnext;// 指向下一个内存块
    char adata[1];// 用于标记分配单元开始的位置
    memoryblock(int nunitsize,int nunitamount):nsize(nunitsize * nunitamount),nfree(nunitamount-1),nfirst(1),pnext(NULL)
    // nunitsize 存储单位的大小 即 sizeof(T) nunitamount 内存块规模
    // nfree 空闲存储单位为 nunitamount-1 nfirst 初值为1 pnext指向下一个内存块的指针为NULL
    {
        //为空闲区编号
      char *ppdata=adata;
      cout<<"存储区首指针 ppdate ="<<(int*)ppdata<<endl;
      for(int i=1;i<nunitamount;i++)
      {
          //当前存储单位存储下一个可分配单位的序号
          (*(unsigned short *)ppdata)=i;
          cout<<"下一个可分配单位序号: "<<(int)*ppdata<<endl;
          //移动指针使之指向下一个单位
          ppdata+=nunitsize;
      }
      cout<<"-------------调用内存块的构造函数-------------------"<<endl;
    }
      void *operator new(size_t,int nunitsize,int nunitamount)
      {
          cout<<"分配内存并创建memoryblock 对象"<<endl;
          return ::operator new(sizeof(memoryblock)+nunitsize*nunitamount);
      }
      void operator delete(void *pblock)
      {
          ::operator delete(pblock);
          cout<<"---------------调用内存块的析构函数----------------"<<endl;
      }
};
///内存池信息表
template<typename T>
struct memorypool
{
    int ninitsize;///首块长度
    int ngrowsize;///后续块长度
    int nunitsize;/// 定义存储单位大小
    memoryblock<T>*pblock;
memorypool(int _ngrowsize=10,int _ninitsize=3)
{
    cout<<"-----------------调用内存池的构造函数---------------"<<endl;
    ninitsize=_ninitsize;
    ngrowsize=_ngrowsize;
    pblock=NULL;
    nunitsize=sizeof(T);
    if(sizeof(T)>4)///调整存储单位的大小
        nunitsize=(sizeof(T)+(MEMPOOL_ALIGNMENT-1)) & ~(MEMPOOL_ALIGNMENT-1);// 返回值为8的倍数 所谓的内存对齐
    else if(sizeof(T)<2)
        nunitsize=2;
    else
        nunitsize=4;
}
~memorypool()
{
    memoryblock<T>*pmyblock=pblock;
    while(pmyblock!=NULL)
    {
        pmyblock=pmyblock->pnext;
        delete(pmyblock);
    }
    cout<<"--------------------调用内存池的析构函数-------------------"<<endl;
}
///用于向内存池请求存储单位的函数,也是内存池向用户提供的主要服务
///遍历内存块链表,找到nfree 大于0 即有空闲的内存块 并从其上分配存储单位
///然后将memoryblock 中nfree 减一 并修改空闲存储单位链表头指针 nfirst 的值
void *allocate(size_t num)
{
    for(int i=0;i<num;++i)
    {
    if(NULL==pblock)
    {
        ///创建首内存块
        pblock=(memoryblock<T>*)new (nunitsize,ninitsize)
        memoryblock<T>(nunitsize,ninitsize);
        return (void*)pblock->adata;///返回内存块中数据元素存储区指针
    }
    ///为内存寻找符合条件的内存块
    memoryblock<T>*pmyblock=pblock;
    while(pmyblock!=NULL&&0==pmyblock->nfree)
        pmyblock=pmyblock->pnext;
    if(pmyblock!=NULL)
    {
        cout<<"找到内存空间 first= "<<pmyblock->nfirst<<endl;
        ///找到后进行内存分配
        char *pfree=pmyblock->adata+pmyblock->nfirst*nunitsize;
        pmyblock->nfirst=*((unsigned short*)pfree);
        pmyblock->nfree--;
        ///返回找到的存储单位指针
        return (void*)pfree;
    }
    else
    {
        ///没有找到 说明当前内存块已用完
        if(0==ngrowsize)return NULL;
        cout<<" 否则分配新内存块"<<endl;
        //分配一个后续内存块
        pmyblock=(memoryblock<T>*)new(nunitsize,ngrowsize)
        memoryblock<T>(nunitsize,ngrowsize);
        if(NULL==pmyblock)
            return NULL;///失败
        pmyblock->pnext=pblock;
        pblock=pmyblock;
        //返回新内存的的存储区指针
        return (void*)pmyblock->adata;
    }
}
}
//free 函数用于释放内存块 该函数根据pfree 的值找到他所在的内存块 然后将它的序号作为nfirst的值(因为他绝对空闲)
//在pfree 的头两个字节内写入原来nfirst 的值。然后要判断 该block 是否全部为free 方法是检测nfree*nunitsize==size
// 若是 则向系统释放内存 若不是 则将该block 放到链表的头部 因为该block上一定含有内存单元 这样可以减少分配是遍历
//链表所消耗的时间
void free(void *pfree)
{
    //找到p所在的块
    cout<<"释放存储单位内存空间"<<endl;
    memoryblock<T>*pmyblock=pblock;
    memoryblock<T>*preblock=NULL;
    while(pmyblock!=NULL&&(pblock->adata>pfree||pmyblock->adata+pmyblock->nsize))
    {
        preblock=pmyblock;
        pmyblock=pmyblock->pnext;
    }
    //该内存块在被内存池pmyblock 所指向的内存块中
    if(NULL!=pmyblock)
    {
        //1 修改数组链表
        *((unsigned short*)pfree)=pmyblock->nfirst;
        pmyblock->nfirst=(unsigned short)((unsigned long)pfree-(unsigned long)pmyblock->adata)/nunitsize;
        pmyblock->nfree++;
        // 2 判断是否需要向系统释放内存
        if(pmyblock->nsize==pmyblock->nfree*nunitsize)
        {
            //在链表中删除
            delete(pmyblock);
        }
        else
        {
            //将该block 插入队首
            preblock=pmyblock->pnext;
            pmyblock->pnext=pblock;
            pblock=pmyblock;
        }
    }
}
};
class user
{
    int s;
    double s1;
    double s3;
public:
    user(int x):s(x)
    {
        cout<<"-------------------调用 user 的析构函数----------------"<<endl;
    }
    int get()
    {
        return s;
    }
    ~user()
    {
        cout<<"------------------调用user 的析构函数------------------"<<endl;
    }
};
int main()
{
    memorypool<user>m_pool;
    user*dp1=(user*)m_pool.allocate(1);
    cout<<"dp1= "<<dp1<<endl;
    new(dp1)user(1111);
    cout<<" 对象中的数据值为 "<<dp1->get()<<endl;
    user*dp2=(user*)m_pool.allocate(1);
    cout<<"dp2= "<<dp2<<endl;
    new(dp2)user(2222);
    cout<<"对象中的数据值为 "<<dp2->get()<<endl;
     user*dp3=(user*)m_pool.allocate(1);
    cout<<"dp3= "<<dp3<<endl;
    new(dp3)user(3333);
    cout<<"对象中的数据值为 "<<dp3->get()<<endl;
     user*dp4=(user*)m_pool.allocate(1);
    cout<<"dp4= "<<dp4<<endl;
    new(dp4)user(4444);
    cout<<"对象中的数据值为 "<<dp4->get()<<endl;
     user*dp5=(user*)m_pool.allocate(1);
    cout<<"dp5= "<<dp5<<endl;
    new(dp5)user(5555);
    cout<<"对象中的数据值为 "<<dp5->get()<<endl;
     user*dp6=(user*)m_pool.allocate(1);
    cout<<"dp6= "<<dp6<<endl;
    new(dp6)user(6666);
    cout<<" 对象中的数据值为 "<<dp6->get()<<endl;
     user*dp7=(user*)m_pool.allocate(1);
   cout<<"dp7= "<<dp7<<endl;
    new(dp7)user(7777);
    cout<<" 对象中的数据值为 "<<dp7->get()<<endl;
    user*dp8=(user*)m_pool.allocate(1);
   cout<<"dp8= "<<dp8<<endl;
    new(dp8)user(8888);
    cout<<" 对象中的数据值为 "<<dp8->get()<<endl;
     user*dp9=(user*)m_pool.allocate(1);
    cout<<"dp9= "<<dp9<<endl;
    new(dp9)user(9999);
    cout<<" 对象中的数据值为 "<<dp9->get()<<endl;
     user*dp10=(user*)m_pool.allocate(1);
    cout<<"dp10= "<<dp10<<endl;
    new(dp10)user(11111);
    cout<<" 对象中的数据值为 "<<dp10->get()<<endl;
     user*dp11=(user*)m_pool.allocate(1);
    cout<<"dp11= "<<dp11<<endl;
    new(dp11)user(22222);
    cout<<" 对象中的数据值为 "<<dp11->get()<<endl;
     user*dp12=(user*)m_pool.allocate(1);
    cout<<"dp12= "<<dp12<<endl;
    new(dp12)user(33333);
    cout<<" 对象中的数据值为 "<<dp12->get()<<endl;
     user*dp13=(user*)m_pool.allocate(1);
    cout<<"dp13= "<<dp13<<endl;
    new(dp13)user(44444);
    cout<<" 对象中的数据值为 "<<dp13->get()<<endl;
     user*dp14=(user*)m_pool.allocate(1);
    cout<<"dp14= "<<dp14<<endl;
    new(dp14)user(55555);
    cout<<" 对象中的数据值为 "<<dp14->get()<<endl;
     user*dp15=(user*)m_pool.allocate(1);
    cout<<"dp15= "<<dp15<<endl;
    new(dp15)user(66666);
    cout<<" 对象中的数据值为 "<<dp15->get()<<endl;
    return 0;
}

案例题目:频繁的内存操作new,delete是比较耗时的操作,为了减少这些操作,一般都会设计自己的内存分配器。一个程序需要频繁使用大小在512Byte到200KByte不定长的内存,请您设计一个高效的内存分配器?

考虑设计的重要关注点和大致思路,空间使用率等。

相关文章
|
5天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
18 4
|
1月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
99 21
|
25天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
28天前
|
存储 C语言 C++
【C++打怪之路Lv6】-- 内存管理
【C++打怪之路Lv6】-- 内存管理
35 0
【C++打怪之路Lv6】-- 内存管理
|
1月前
|
存储 C语言 C++
【C/C++内存管理】——我与C++的不解之缘(六)
【C/C++内存管理】——我与C++的不解之缘(六)
|
1月前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
62 1
|
1月前
|
C++
C/C++内存管理(下)
C/C++内存管理(下)
46 0
|
1月前
|
存储 Linux C语言
C/C++内存管理(上)
C/C++内存管理(上)
35 0
|
1月前
|
Linux C++
Linux c/c++文件虚拟内存映射
这篇文章介绍了在Linux环境下,如何使用虚拟内存映射技术来提高文件读写的速度,并通过C/C++代码示例展示了文件映射的整个流程。
45 0
|
4天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
21 4