当前位置: 首页 > news >正文

101 C++内存高级话题 内存池概念,代码实现和详细分析

零 为什么要用内存池?

从前面的知识我们知道,当new 或者 malloc 的时候,假设您想要malloc 10个字节,

char * pchar = new char[10];

char *pchar1 = malloc(10);

实际上编译器为了 记录和管理这些数据,做了不少事情,类似这张图。

从上述看到,每new 一个 class都会使用这些字节:

4+(30到60)+(真的分配的10字节)(10到几十个)+4

也就是说:为了这10个字节,实际上背后服务的有更多的字节。

如果在某一个场景下,我们需要new 出来大量的class,例如卡牌的10连抽,100连抽,那么每次new一个class,都会有大量的背后服务的字节使用。有没有一种方法可以减少这种背后服务的字节数量呢?

因此C++的前辈们就搞了一个内存池。

一 内存池的概念和实现原理概述

概念:当malloc 或者 new 的时候,为了节约内存,创建了内存池的方法。当阅读完下面的原理后,还会知道会节省malloc或者new 的次数,因此也会提高效率,但是提高的不多。(提高不多的原因有两点:1,本身malloc 和new 执行效率就很高,2,因此内存池的内部实现需要使用链表将创建的真正使用的内存串联起来,这个串的过程也要花费时间)

我们假设之前class 是占用8个字节,new一次会出来对我们有用的8个字节的class,

4+(30到60)+(真的分配的8字节)(10到几十个)+4

但是有更多的字节是为这8个字节服务,现在通过重写 operator new 的方法,让一次new 出来80个字节

4+(30到60)+真的分配的80字节(10到几十个)+4

这就存在着另一个问题,我们每次实际上是需要8个字节的,因此需要将这80个字节,怎么串起来,让每次都拿8个字节,

还存在着另一个问题,就是这8个字节的回收,因此 还需要 重写 operator delete的方法实现。

注意的点:一般创建内存时,会创建一个class 的整倍数。

使用链表将其串联起来。

operator delete的时候,实际上是让串联的指针重新指,而不是真的 free 或者delete。

因此理论上:内存池始终都会拿着申请的内存。不会真正的释放。

二 针对一个类的内存池实现演示代码

//内存池代码演示,Teacher36我们看做是一个卡牌类,这个卡牌类,肯定有如下的类型:红,橙,绿,蓝四种类型分别代表不同的级别。肯定还有名字,技能1,技能2,技能3,技能4,属性(金木水火土)。
//假设我们这个 卡牌每天都会给用户免费抽3次,vip用户每天抽10次,
//那就意味着,服务器每天要处理大量的抽卡行为,这就能用到 内存池技术了。class Teacher36 {
public:int jibie; //红 ,橙,绿,蓝string name; // 名字,int jineng1; //技能1int jineng2;//技能2int jineng3;//技能3int jineng4;//技能4int shuxing;// 属性 金木水火土 //我们还需要自己做一下统计, new Teacher 一次,统计一次。static int m_iCout;static int m_iMallocCount;//malloc一次,统计一次,每次malloc,会分配10个 * Teacher36的大小。
private:Teacher36 *next;//作用是 将 new 出来的字节挨个 链接起来static Teacher36* m_FreePosi;//总是指向一块可以分配出去的内存的首地址static int m_sTrunkCount;// 一次分配多少倍的该类内存
public://第1步.重写operator new 函数static void *operator new(size_t size) {//1.0 之前的写法//Teacher36 *pTeacher36 = (Teacher36*)malloc(sizeof(Teacher36));//return pTeacher36;//1.1 现在的写法,不能只malloc 一个 Teacher36的size,要malloc一堆//假设一次弄10个Teacher36的大小,return那个出去呢?//很显然,总是要弄一个 Teacher36 * 返回出去。//很显然,弄一个出去,剩余的9个怎么弄呢?//这就存在将malloc的这10个Teacher 管理起来的逻辑,这里需要一个链表,将剩余的9个链接起来,我们通过 Teacher36 * next完成//然后让最后一个Teacher36 * 指向 nullptrTeacher36* templink;//让这个templink始终指向可以返回出去的Teacher36 *if (m_FreePosi==nullptr) {//当m_FreePosi为null的时候,代表一定要申请内存,且要申请一大块你内存//注意:从上一节的知识我们知道 ,这里参数 size就是一个 Teacher36的大小。//cout << "size = " << size <<  "  m_sTrunkCount = " << m_sTrunkCount << endl;size_t realSize = m_sTrunkCount * size;//创建 realSize大小的空间,并且将这个空间强转成m_FreePosi = reinterpret_cast<Teacher36 *>(new char[realSize]);templink = m_FreePosi;//把分配出来的这一大块内存(10小块)彼此要链接起来,方便后来使用,templink指向的是for (; templink!= &m_FreePosi[m_sTrunkCount - 1];++templink) {templink->next = templink + 1;}//让最后一个链 的next指向nulltemplink->next = nullptr;++m_iMallocCount;//统计一下malloc的次数}//当m_FreePosi存在空间的时候,就把m_FreePosi给 templink,然后将templink返回出去templink = m_FreePosi;//既然当前的 m_FreePosi的一小块被返回出去了,那么下一次,就要返回m_FreePosi的next,因此这里还需要将m_FreePosi = m_FreePosi->next;m_FreePosi = m_FreePosi->next;//然后我们再记录一下, new 了多少次Teacher36++m_iCout;return templink;}//第2步.重写 operator delete函数,注意和之前的不同,这里不是直接销毁这块static void operator delete(void *phead) {//2.0之前的写法,是要真的free掉这块内存//free(phead);//return;//2.1 让被释放的phead的next指向 m_FreePosi,然后让phead变成m_FreePosistatic_cast<Teacher36*>(phead)->next = m_FreePosi;m_FreePosi = static_cast<Teacher36 *>(phead);}
};int Teacher36::m_iCout = 0;//new 的次数
int Teacher36::m_iMallocCount = 0;//malloc的次数
Teacher36* Teacher36::m_FreePosi = nullptr;//第一次肯定是没有数据的,也就是指向nullptr
int Teacher36::m_sTrunkCount = 10;//一次分配多少倍的空间void main() {cout << sizeof(int) << endl; //4cout << sizeof(int*) << endl; //4cout << sizeof(long) << endl; //4cout << sizeof(long*) << endl; //4cout << sizeof(string) << endl;//28cout << "sizeof(Teacher36) = " << sizeof(Teacher36)<< endl;//56for (int i = 0; i < 1000;i++) {Teacher36 *ptea = new Teacher36;}cout << "申请分配内存的次数为:" << Teacher36::m_iCout <<endl;cout << "实际malloc内存的次数为:" << Teacher36::m_iMallocCount << endl;
}

operator new 的代码说明

我们已每次申请 5个单位的Teacher36说明

当5个都用完的时候,再来new Teacher,就继续申请一块内存

operator delete 的代码说明:

假设申请了2次 malloc了,且已经new 了9次代码了

        //2.1 让被释放的phead的next指向 m_FreePosi,然后让phead变成m_FreePosi
        static_cast<Teacher36*>(phead)->next = m_FreePosi;
        m_FreePosi = static_cast<Teacher36 *>(phead);

三,内存池代码后续说明。

我们代码改动一下,每次分配的内存为5个teacher36的大小,new 20次,观察log会发现,每5个之间都是56个字节。

注意的是:在上述的代码,实际上我们并没有释放内存。一直在持有这些内存,因此使用内存池要注意这一点。

四。嵌入式指针。embedded pointer

从上面的代码中可以看出,实际上需要一个指针,Teacher36 * next。这4个字节是为了将管理分配出来的内存而写的,实际上是可以不需要的。

那么我们这里就要引入一个 嵌入式指针的概念。

实际上,在内存池的代码中,通常结合 嵌入式 指针来工作。

相关原理:

借用Teacher36所占用空间的前4个字节,当做指针,链接管理后续分配的内存。这样 就可以省下来这个next指针。

从上述原理,我们也可以看出,如果要使用嵌入式指针,class 类的大小需要大于等于4个字节。

也就是不能是空类,空类只占用1个字节。

代码实现;

//嵌入式指针.
//struct obj 放在类外边和放在类里面是一样的。,但是一般都会放在类里面,因此称为嵌入式指针。
class Teacher37 {public:int m_i;int m_j;public:struct obj {struct obj *next;//这个next就是个嵌入式指针。//自己是一个obj结构对象,//那么把自己这个对象的next指针指向另外一个obj结构对象,//最终,把多个自己这种类型的对象通过链串起来};
};void main() {cout << sizeof(Teacher37) << endl;//8 ,两个int 各占4个字节。struct obj是类型声明,是不占用空间的。
}

五。利用嵌入式指针改动内存池代码。

这里要解决两个问题:

1.单独的为内存池技术来写一个类,不是在单独的Teacher36上写,也不是为单独的Teacher37写。而是单独的写一个通用的类

2.使用嵌入式指针改动代码。

//专门的内存池类
class myallocator { //必须保证应用本类的类的sizeof 不少于4个字节,否则会报错public://分配内存接口void *allocate(size_t size) {obj *tmplink;if (m_FreePosi == nullptr) {//为空,我要申请内存,要申请一大块内存size_t realsize = m_sTrunkCout * size;//申请m_sTrunkCout 这么多倍的内存m_FreePosi = (obj *)malloc(realsize);tmplink = m_FreePosi;//把分配出来的这一大块内存(5小块),彼此链接起来,供后续使用for (int i = 0; i < m_sTrunkCout - 1;++i) {tmplink->next = (obj *)((char *)tmplink + size);tmplink = tmplink->next;}//end fortmplink->next = nullptr;}tmplink = m_FreePosi;m_FreePosi = m_FreePosi->next;return tmplink;}void deallocate(void *phead) {((obj *)phead)->next = m_FreePosi;m_FreePosi = (obj *)phead;}private://写在类内的结构,这样只让其在类内使用struct obj {struct obj *next;};int m_sTrunkCout = 5;obj* m_FreePosi = nullptr;//始终指向下一个即将分配出去的内存
};//怎么使用这个专门的myallocator类呢?假设Teacher38是要用的类
class Teacher38 {
public:int leixing;int jineng;public:static myallocator myalloc;//声明静态成员变量//重写 operator new static void* operator new(size_t size) {return myalloc.allocate(size);}//从写 operator deletestatic void operator delete(void *phead) {return myalloc.deallocate(phead);}
};
myallocator Teacher38::myalloc;//定义静态变量void main() {Teacher38 *tea[20];//这里为什么用 ++i,而不是i++呢?效果一样,不同的是 ++i会右值,会少产生一次中间变量for (int i = 0; i < 15;++i) {tea[i] = new Teacher38();printf("tea[%d] = %p\n", i, tea[i]);}for (int i = 0; i < 15; ++i) {delete tea[i];}
}

tea[0] = 034971F0
tea[1] = 034971F8
tea[2] = 03497200
tea[3] = 03497208
tea[4] = 03497210
tea[5] = 03495F48
tea[6] = 03495F50
tea[7] = 03495F58
tea[8] = 03495F60
tea[9] = 03495F68
tea[10] = 034970A8
tea[11] = 034970B0
tea[12] = 034970B8
tea[13] = 034970C0
tea[14] = 034970C8

相关文章:

101 C++内存高级话题 内存池概念,代码实现和详细分析

零 为什么要用内存池&#xff1f; 从前面的知识我们知道&#xff0c;当new 或者 malloc 的时候&#xff0c;假设您想要malloc 10个字节&#xff0c; char * pchar new char[10]; char *pchar1 malloc(10); 实际上编译器为了 记录和管理这些数据&#xff0c;做了不少事情&…...

算计是一种混合了感性和理性的非纯粹逻辑系统

算计是人类带有动因的感性与理性混合超&#xff08;计&#xff09;算&#xff0c;是还未形成逻辑状态的非逻辑系统。算计是指人类在进行决策、推理、思考等活动时&#xff0c;融合了感性和理性的思维过程。它是一种超越纯粹逻辑思维的综合性思维方式。感性是指个体基于感觉、直…...

Python 处理小样本数据的文档分类问题

在处理小样本数据的文档分类问题时&#xff0c;可以尝试使用迁移学习或者基于预训练模型的方法&#xff0c;如BERT、GPT等。然而&#xff0c;直接在这里编写一个完整的深度学习文档分类代码超出了这个平台的限制&#xff0c;但我可以为你提供一个基本的思路和简单示例&#xff…...

centos7安装oracle

1 安装虚拟机 设置4G内存&#xff0c;硬盘40G 2 配置网络环境 2.1配置主机名 # vi /etc/hostname 修改为 oracle2.2 配置IP地址 # vi /etc/sysconfig/network-scripts/ifcfg-ens33 修改 BOOTPROTO"static" ONBOOT"yes" IPADDR192.168.109.110 NETMAS…...

Web html

目录 1 前言2 HTML2.1 元素(Element)2.1.1 块级元素和内联(行级)元素2.1.2 空元素 2.2 html页面的文档结构2.3 常见标签使用2.3.1 注释2.3.2 标题2.3.3 段落2.3.4 列表2.3.5 超链接2.3.6 图片2.3.7 内联(行级)标签2.3.8 换行 2.4 属性2.4.1 布尔属性 2.5 实体引用2.6 空格2.7 D…...

Go语言学习踩坑记

go: go.mod file not found in current directory or any parent directory; see go help mod 解决 资源下载&#xff1a; 序号文件地址1 1、Go IDE liteidex38.3-win64-qt5.15.2.zip Release x38.3 visualfc/liteide GitHub2 2、Go语言的编译环境 go1.21.6.windows-amd64.m…...

Vue-easy-tree封装及使用

1.使用及安装 下载依赖 npm install wchbrad/vue-easy-tree引入俩种方案 1.在main.js中引入 import VueEasyTree from "wchbrad/vue-easy-tree"; import "wchbrad/vue-easy-tree/src/assets/index.scss" Vue.use(VueEasyTree)2.当前页面引入 import VueEa…...

opencv中使用cuda加速图像处理

opencv大多数只使用到了cpu的版本&#xff0c;实际上对于复杂的图像处理过程用cuda&#xff08;特别是高分辨率的图像&#xff09;可能会有加速效果。是否需要使用cuda需要思考&#xff1a; 1、opencv的cuda库是否提供了想要的算子。在CUDA-accelerated Computer Vision你可以…...

FPGA高端项目:IMX327 MIPI 视频解码 USB3.0 UVC 输出,提供FPGA开发板+2套工程源码+技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的 MIPI 编解码方案 3、本 MIPI CSI-RX IP 介绍4、个人 FPGA高端图像处理开发板简介5、详细设计方案设计原理框图IMX327 及其配置MIPI CSI RX图像 ISP 处理图像缓存UVC 时序USB3.0输出架构 6、vivado工程详解FPGA逻辑设计 7、工…...

深入探索 MySQL 8 中的 JSON 类型:功能与应用

随着 NoSQL 数据库的兴起&#xff0c;JSON 作为一种轻量级的数据交换格式受到了广泛的关注。为了满足现代应用程序的需求&#xff0c;MySQL 8引入了原生的 JSON 数据类型&#xff0c;提供了一系列强大的 JSON 函数来处理和查询 JSON 数据。本文将深入探讨 MySQL 8 中JSON 类型的…...

学习Spring的第十三天

非自定义bean注解开发 设置非自定义bean : 用bean去修饰一个方法 , 最后去返回 , spring就把返回的这个对象,放到Spring容器 一 :名字 : 如果bean配置了参数 , 名字就是参数名 , 如果没有 , 就是方法名字 二 : 如果方法产生对象时 , 需要注入数据 , 在方法参数设置即可 , …...

jss/css/html 相关的技术栈有哪些?

js 的技术组件有哪些&#xff1f;比如 jQuery vue 等 常见的JavaScript技术组件&#xff1a; jQuery&#xff1a; jQuery是一个快速、小巧且功能丰富的JavaScript库&#xff0c;用于简化DOM操作、事件处理、动画效果等任务。 React&#xff1a; React是由Facebook开发的用于构…...

机器学习超参数优化算法(贝叶斯优化)

文章目录 贝叶斯优化算法原理贝叶斯优化的实现&#xff08;三种方法均有代码实现&#xff09;基于Bayes_opt实现GP优化基于HyperOpt实现TPE优化基于Optuna实现多种贝叶斯优化 贝叶斯优化算法原理 在贝叶斯优化的数学过程当中&#xff0c;我们主要执行以下几个步骤&#xff1a; …...

Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(六)

原文&#xff1a;Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第十四章&#xff1a;使用卷积神经网络进行深度计算机视觉 尽管 IBM 的 Deep Blue 超级计算机在 1996 年击败了国际象棋世界冠军…...

XGB-3: 模型IO

在XGBoost 1.0.0中&#xff0c;引入了对使用JSON保存/加载XGBoost模型和相关超参数的支持&#xff0c;旨在用一个可以轻松重用的开放格式取代旧的二进制内部格式。后来在XGBoost 1.6.0中&#xff0c;还添加了对通用二进制JSON的额外支持&#xff0c;作为更高效的模型IO的优化。…...

springboot(ssm船舶维保管理系统 船只报修管理系统Java系统

springboot(ssm船舶维保管理系统 船只报修管理系统Java系统 开发语言&#xff1a;Java 框架&#xff1a;springboot&#xff08;可改ssm&#xff09; vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 5.7&a…...

机器学习本科课程 大作业 多元时间序列预测

1. 问题描述 1.1 阐述问题 对某电力部门的二氧化碳排放量进行回归预测&#xff0c;有如下要求 数据时间跨度从1973年1月到2021年12月&#xff0c;按月份记录。数据集包括“煤电”&#xff0c;“天然气”&#xff0c;“馏分燃料”等共9个指标的数据&#xff08;其中早期的部分…...

[office] excel中weekday函数的使用方法 #学习方法#微信#媒体

excel中weekday函数的使用方法 在EXCEL中Weekday是一个日期函数&#xff0c;可以计算出特定日期所对应的星期数。下面给大家介绍下Weekday函数作用方法。 01、比如&#xff0c;我在A84单元格输入一个日期&#xff0c;2018/5/9&#xff1b;那么&#xff0c;我们利用weekday计算…...

PAT-Apat甲级题1007(python和c++实现)

PTA | 1007 Maximum Subsequence Sum 1007 Maximum Subsequence Sum 作者 CHEN, Yue 单位 浙江大学 Given a sequence of K integers { N1​, N2​, ..., NK​ }. A continuous subsequence is defined to be { Ni​, Ni1​, ..., Nj​ } where 1≤i≤j≤K. The Maximum Su…...

洛谷:P2957 [USACO09OCT] Barn Echoes G

题目描述 The cows enjoy mooing at the barn because their moos echo back, although sometimes not completely. Bessie, ever the excellent secretary, has been recording the exact wording of the moo as it goes out and returns. She is curious as to just how mu…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

什么是EULA和DPA

文章目录 EULA&#xff08;End User License Agreement&#xff09;DPA&#xff08;Data Protection Agreement&#xff09;一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA&#xff08;End User License Agreement&#xff09; 定义&#xff1a; EULA即…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

Angular微前端架构:Module Federation + ngx-build-plus (Webpack)

以下是一个完整的 Angular 微前端示例&#xff0c;其中使用的是 Module Federation 和 npx-build-plus 实现了主应用&#xff08;Shell&#xff09;与子应用&#xff08;Remote&#xff09;的集成。 &#x1f6e0;️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...