c++——内存池介绍

c++——内存池介绍1 默认内存管理函数的不足利用默认的内存管理操作符 new delete 和函数 malloc free 在堆上分配和释放内存会有一些额外的开销 系统在接收到分配一定大小内存的请求时 首先查找内部维护的内存空闲块表 并且需要根据一定的算法 例如分配最先找到的不小于申请大小的内存块给请求者 或者分配最适于申请大小的内存块 或者分配最大空闲的内存块等 找到合适大小的空闲内存块 如果该空闲内存块过大 还需要切割成已分配的部分和较小的空闲块 然后系统更新内存空闲块表 完成一次内存分配 类似地 在释放内存时

1.默认内存管理函数的不足

利用默认的内存管理操作符new/delete和函数malloc()/free()在堆上分配和释放内存会有一些额外的开销。

系统在接收到分配一定大小内存的请求时,首先查找内部维护的内存空闲块表,并且需要根据一定的算法(例如分配最先找到的不小于申请大小的内存块给请求者,或者分配最适于申请大小的内存块,或者分配最大空闲的内存块等)找到合适大小的空闲内存块。如果该空闲内存块过大,还需要切割成已分配的部分和较小的空闲块。然后系统更新内存空闲块表,完成一次内存分配。类似地,在释放内存时,系统把释放的内存块重新加入到空闲内存块表中。如果有可能的话,可以把相邻的空闲块合并成较大的空闲块。默认的内存管理函数还考虑到多线程的应用,需要在每次分配和释放内存时加锁,同样增加了开销。

可见,如果应用程序频繁地在堆上分配和释放内存,会导致性能的损失。并且会使系统中出现大量的内存碎片,降低内存的利用率。默认的分配和释放内存算法自然也考虑了性能,然而这些内存管理算法的通用版本为了应付更复杂、更广泛的情况,需要做更多的额外工作。而对于某一个具体的应用程序来说,适合自身特定的内存分配释放模式的自定义内存池可以获得更好的性能。

2.内存池简介

2.1 内存池的定义

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

2.2 内存池的优点

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

2.3 内存池的分类

应用程序自定义的内存池根据不同的适用场景又有不同的类型。从线程安全的角度来分,内存池可以分为单线程内存池和多线程内存池。单线程内存池整个生命周期只被一个线程使用,因而不需要考虑互斥访问的问题;多线程内存池有可能被多个线程共享,因此需要在每次分配和释放内存时加锁。相对而言,单线程内存池性能更高,而多线程内存池适用范围更加广泛。

从内存池可分配内存单元大小来分,可以分为固定内存池和可变内存池。所谓固定内存池是指应用程序每次从内存池中分配出来的内存单元大小事先已经确定,是固定不变的;而可变内存池则每次分配的内存单元大小可以按需变化,应用范围更广,而性能比固定内存池要低。

3.经典的内存池技术

内存池技术因为其对内存管理有着显著的优点,在各大项目中广泛应用,备受推崇。但是,通用的内存管理机制要考虑很多复杂的具体情况,如多线程安全等,难以对算法做有效的优化,所以,在一些特殊场合,实现特定应用环境的内存池在一定程度上能够提高内存管理的效率。

经典内存池技术,是一种用于分配大量大小相同的小对象的技术。通过该技术可以极大加快内存分配/释放过程。既然是针对特定对象的内存池,所以内存池一般设置为类模板,根据不同的对象来进行实例化。

3.1 经典内存池的设计

3.1.1 经典内存池实现过程

如上图所示,申请的内存块存放三个可供分配的空闲节点。空闲节点由空闲节点链表管理,如果分配出去,将其从空闲节点链表删除,如果释放,将其重新插入到链表的头部。如果内存块中的空闲节点不够用,则重新申请内存块,申请的内存块由内存块链表来管理。

注意,本文涉及到的内存块链表和空闲内存节点链表的插入,为了省去遍历链表查找尾节点,便于操作,新节点的插入均是插入到链表的头部,而非尾部。当然也可以插入到尾部,读者可自行实现。

3.1.2 经典内存池数据结构设计

按照上面的过程设计,内存池类模板有这样几个成员。

两个指针变量:
内存块链表头指针:pMemBlockHeader;
空闲节点链表头指针:pFreeNodeHeader;




空闲节点结构体:

struct FreeNode { FreeNode* pNext; char data[ObjectSize]; }; 

内存块结构体:

struct MemBlock { MemBlock *pNext; FreeNode data[NumofObjects]; }; 

3.2 经典内存池的实现

根据以上经典内存池的设计,编码实现如下。

#include 
  
    using namespace std; template 
   
     class MemPool { private: //空闲节点结构体 struct FreeNode { FreeNode* pNext; char data[ObjectSize]; }; //内存块结构体 struct MemBlock { MemBlock* pNext; FreeNode data[NumofObjects]; }; FreeNode* freeNodeHeader; MemBlock* memBlockHeader; public: MemPool() { freeNodeHeader = NULL; memBlockHeader = NULL; } ~MemPool() { MemBlock* ptr; while (memBlockHeader) { ptr = memBlockHeader->pNext; delete memBlockHeader; memBlockHeader = ptr; } } void* malloc(); void free(void*); }; //分配空闲的节点 template 
    
      void* MemPool 
     
       ::malloc() { //无空闲节点,申请新内存块 if (freeNodeHeader == NULL) { MemBlock* newBlock = new MemBlock; newBlock->pNext = NULL; freeNodeHeader=&newBlock->data[0]; //设置内存块的第一个节点为空闲节点链表的首节点 //将内存块的其它节点串起来 for (int i = 1; i < NumofObjects; ++i) { newBlock->data[i - 1].pNext = &newBlock->data[i]; } newBlock->data[NumofObjects - 1].pNext=NULL; //首次申请内存块 if (memBlockHeader == NULL) { memBlockHeader = newBlock; } else { //将新内存块加入到内存块链表 newBlock->pNext = memBlockHeader; memBlockHeader = newBlock; } } //返回空节点闲链表的第一个节点 void* freeNode = freeNodeHeader; freeNodeHeader = freeNodeHeader->pNext; return freeNode; } //释放已经分配的节点 template 
      
        void MemPool 
       
         ::free(void* p) { FreeNode* pNode = (FreeNode*)p; pNode->pNext = freeNodeHeader; //将释放的节点插入空闲节点头部 freeNodeHeader = pNode; } class ActualClass { static int count; int No; public: ActualClass() { No = count; count++; } void print() { cout << this << ": "; cout << "the " << No << "th object" << endl; } void* operator new(size_t size); void operator delete(void* p); }; //定义内存池对象 MemPool 
        
          mp; void* ActualClass::operator new(size_t size) { return mp.malloc(); } void ActualClass::operator delete(void* p) { mp.free(p); } int ActualClass::count = 0; int main() { ActualClass* p1 = new ActualClass; p1->print(); ActualClass* p2 = new ActualClass; p2->print(); delete p1; p1 = new ActualClass; p1->print(); ActualClass* p3 = new ActualClass; p3->print(); delete p1; delete p2; delete p3; } 
         
        
       
      
     
    
  

3.3 程序分析

(2)成员指针变量 memBlockHeader 是用来把所有申请的内存块连接成一个内存块链表,以便通过它可以释放所有申请的内存。freeNodeHeader 变量则是把所有空闲内存节点串成一个链表。freeNodeHeader为空则表明没有可用的空闲内存节点,必须申请新的内存块。

(3)申请空间的过程如下。在空闲内存节点链表非空的情况下,malloc 过程只是从链表中取下空闲内存节点链表的头一个节点,然后把链表头指针移动到下一个节点上去。否则,意味着需要一个新的内存块。这个过程需要申请新的内存块切割成多个内存节点,并把它们串起来,内存池技术的主要开销就在这里。

(4)释放对象的过程就是把被释放的内存节点重新插入到内存节点链表的开头。最后被释放的节点就是下一个即将被分配的节点。

(5)内存池技术申请/释放内存的速度很快,其内存分配过程多数情况下复杂度为 O(1),主要开销在 freeNodeHeader 为空时需要生成新的内存块。内存节点释放过程复杂度为 O(1)。

(6) 在上面的程序中,指针 p1 和 p2 连续两次申请空间,它们代表的地址之间的差值为 8,正好为一个内存节点的大小(sizeof(FreeNode))。指针 p1 所指向的对象被释放后,再次申请空间,得到的地址与刚刚释放的地址正好相同。指针 p3 多代表的地址与前两个对象的地址相聚很远,原因是第一个内存块中的空闲内存节点已经分配完了,p3 指向的对象位于第二个内存块中。

以上内存池方案并不完美,比如,只能单个单个申请对象空间,不能申请对象数组,内存池中内存块的个数只能增大不能减少,未考虑多线程安全等问题。现在,已经有很多改进的方案,请读者自行查阅相关资料。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/225410.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 上午9:23
下一篇 2026年3月17日 上午9:24


相关推荐

  • acwing1057. 股票买卖 IV(状态机模型)

    acwing1057. 股票买卖 IV(状态机模型)给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润,你最多可以完成 k 笔交易。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。一次买入卖出合为一笔交易。输入格式第一行包含整数 N 和 k,表示数组的长度以及你可以完成的最大交易数量。第二行包含 N 个不超过 10000 的正整数,表示完整的数组。输出格式输出一个整数,表示最大利润。数据范围1≤N≤105,1≤k≤100输入样例1:3 22

    2022年8月9日
    6
  • 奇安信发布龙虾安全伴侣,应对 openclaw“养虾潮”新风险

    奇安信发布龙虾安全伴侣,应对 openclaw“养虾潮”新风险

    2026年3月16日
    2
  • 达梦数据库查询语句「建议收藏」

    达梦数据库查询语句「建议收藏」DMSQL简介DM_SQL语言是一种介于关系代数与关系演算之间的语言,其功能主要包括数据定义、查询、操纵和控制四个方面,通过各种不同的SQL语句来实现。按照所实现的功能,DM_SQL语句分为以下几种:用户、模式、基表、视图、索引、序列、全文索引、存储过程和触发器的定义和删除语句,基表、视图、全文索引的修改语句,对象的更名语句;查询(含全文检索)、插入、删除、修改语句;数据库安全语句…

    2025年10月7日
    2
  • 笛卡尔心形函数图像_笛卡尔心形曲线

    笛卡尔心形函数图像_笛卡尔心形曲线js绘制canvas图形varcr=document.getElementById(“cardioid”);varW=cr.width/2,H=cr.height/3,R=150;varc=cr.getContext(“2d”);varG=360,g=0,T=Math.PI*2,t=T/G;c.save();c.translate(W,…

    2022年10月17日
    4
  • 徐磊语法 6 7 时态的正确定义

    徐磊语法 6 7 时态的正确定义时态的正确定义时 指时间状语态 动作和时间状语之间的关系决定态 在前面 在后面 在时间状语这一刻 动作已经是什么样子的 也就是跟时间状语的关系 先通过时 确定时间状语的某一点 在通过态 告诉动作跟这一点的位置关系 间接表达动作发生的准确时间 时的正确定义 现在时 时间状语是现在动作发生在过去 也用 进行态 过去时 时间状语是过去将来时 时间状语是将来过去将来时 时间状语是

    2026年3月18日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号