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

C++——内存池_2

C++内存池

  • 重载 new 和 delete 运算符
  • C++内存池
    • 使用内存池的目的
    • 逐步实现内存池

重载 new 和 delete 运算符

实际开发中,重载newdelete的主要目的是实现内存池。内存池在高性能的服务程序中很常用。点击浏览重载 new 和 delete 运算符的内容,建议先看这部分。

C++内存池

内存池预先分配内存空间。程序启动的时候,先向系统申请一大块连续的空间。在程序中,如果需要用内存, 从内存池中借一块小的空间出来,用完了再还回去。说白了,如果沒有内存池,直接向系统借;如果有内存池,就向内存池借。
程序在运行的过程中,如果内存池太小了,可以扩展。但有一个原则,每次都会向系统申请一大块连续的内存空间。

使用内存池的目的

目的主要有两个:

  1. 提升分配和归还的速度
  • 对高性能的服务程序来说,频繁的new和delete也算浪费资源。
  1. 减少内存碎片
  • 碎片多了,系统管理内存的效率就会下降,系统的性能也会下降。

逐步实现内存池

#include <iostream>using namespace std;class Car
{public:int speed;float acceleration;Car(int sp, float acc){speed = sp, acceleration = acc; cout << "~~ 类的构造 speed: " << speed << " acceleration: " << acceleration << " ~~" << endl;}~Car() {cout << "~~ 调用析构 ~~" << endl;}
};int main() {Car* p = new Car(320, 4.6);cout << "p的地址:" << p << "\nspeed: " << p->speed << " acceleration: " << p->acceleration << endl;delete p;return 0;
}

在上面这段代码中,Car类有两个类型分别是intfloat的成员变量,所以,用Car类创建的对象占用的内存空间是8字节。
我打算预先向系统申请连续的18字节的内存空间,如下图:
内存示意图
也就是说,内存池的大小是18字节。
组织数据的方法是:标志位,1个字节(图中的0号、9号字节),表示后面的空间是否被占用。后面8个(1~8号、10~17号)字节刚好可以存放1个Car类对象。
标志位的取值只有010表示空闲,1表示占用。故这18个字节的内存池,能存放2个Car对象。
当需要为Car对象分配内存的时候,先判断标志位是否被占用,若没有被占用,就把标志位后面的这个内存地址返回。
了解以上这些知识后,来看一段示例代码:

#include <iostream>using namespace std;class Car
{public:int speed;float acceleration;Car(int sp, float acc){speed = sp, acceleration = acc; cout << "~~ 类的构造 speed: " << speed << " acceleration: " << acceleration << " ~~" << endl;}~Car() {cout << "~~ 调用析构 ~~" << endl;}void* operator new(size_t size){cout << "调用类的重载 new: " << size << " 字节" << endl;void* ptr = malloc(size);cout << "申请的地址是:" << ptr << endl;return ptr;}void operator delete(void* ptr){cout << "调用了类的重载 delete" << endl;if (ptr == 0) return;free(ptr);}
};int main() {Car* p = new Car(320, 4.6);cout << "p的地址:" << p << "\nspeed: " << p->speed << " acceleration: " << p->acceleration << endl;delete p;return 0;
}

上面这段代码在C++——内存池_1也用到了,现在在此基础上实现简单的内存池。

首先,内存池需要一个指针来存放它的起始地址。内存池就是一块内存,没有数据类型的说法,用什么类型的指针都可以。在此示例中,用字符更合适,因为字符指针+1就是偏移1个字节。

  • 声明指针,用于存放内存池的起始地址;
  • 定义初始化内存池的函数;
  • 定义释放内存池的函数

接下来修改分配内存的重载函数void* operator new(size_t size),向内存池中申请内存。池中只有2个位置,判断标志位,哪个空闲就返回哪一块。如果没有空闲维位置,可以返回空地址,也可以直接向系统申请内存。
修改申请内存的重载函数,释放内存的函数void operator delete(void* ptr)也一并在下方给出,代码如下:

#include <iostream>
#include <cstring>using namespace std;class Car
{public:int speed;float acceleration;static char* m_pool; // 内存池的起始地址(为什么是静态变量?后面有提到)static bool initpool() // 初始化内存池的函数{m_pool = (char*)malloc(18); // 向系统申请18字节的内存if (m_pool == 0) return false; // 若申请失败,返回 falsememset(m_pool, 0, 18); // 把内存池中的内容初始化为 0cout << "内存池的起始地址:" << (void*)m_pool << endl;return true;}static void freepool() // 释放内存池的函数{if(m_pool == 0) return; // 如果内存池为空,无需释放,直接返回free(m_pool); // 把内存池归还给系统cout << "内存池已释放" << endl;}Car(int sp, float acc){speed = sp, acceleration = acc; cout << "~~ 类的构造 speed: " << speed << " acceleration: " << acceleration << " ~~" << endl;}~Car() {cout << "~~ 调用析构 ~~" << endl;}// 类的静态成员函数 static 所以要把 m_pool 声明为静态变量 初始化函数和释放函数也要声明为 static// C++——内存池_1 这篇文章的最后有讲到这个点void* operator new(size_t size){if(m_pool[0] == 0) // 判断第一位是否空闲 也就是要看内存空间 m_pool 的第一位的标志位 m_pool[0] 是否等于 0{cout << "分配了第一块内存:" << (void*)(m_pool + 1) << endl;m_pool[0] = 1; // 把第一位置标记为已分配return m_pool + 1; // 返回第一个用于存放对象的地址}if(m_pool[9] == 0) // 判断第二位是否空闲{cout << "分配了第二块内存:" << (void*)(m_pool + 10) << endl;m_pool[9] = 1; // 把第一位置标记为已分配return m_pool + 10; // 返回第一个用于存放对象的地址}// 若两个位置都不可用,就直接向系统申请内存void* ptr = malloc(size); // 向系统申请内存cout << "向系统申请到的内存地址:" << ptr << endl;return ptr;}void operator delete(void* ptr){if(ptr == 0) return; // 若传进来的地址为空,直接返回。if(ptr == m_pool + 1) // 若传进来的地址是内存池的第一个位置{cout << "释放第一块内存!" << endl;m_pool[0] = 0; // 把第一个位置标记为空闲return; // 归还内存之后,函数应该立即返回}if(ptr == m_pool + 10) {cout << "释放第二块内存!" << endl;m_pool[9] = 0; // 把第二个位置标记为空闲return; // 归还内存之后,函数应该立即返回}// 若传进来的地址不属于内存池,把它归还系统。free(ptr); // 释放内存}
};// 内存池的指针 m_pool 是静态成员变量,需要在 main 函数外面初始化。
char* Car::m_pool = 0; // 初始化内存池指针int main() {if(Car::initpool() == false) {cout << "初始化内存池失败!!!" << endl; return -1;}Car* p1 = new Car(320, 4.6);cout << "p1的地址:" << p1 << "\nspeed: " << p1->speed << " acceleration: " << p1->acceleration << endl;Car* p2 = new Car(220, 4.0);cout << "p2的地址:" << p2 << "\nspeed: " << p2->speed << " acceleration: " << p2->acceleration << endl;Car* p3 = new Car(520, 2.9);cout << "p3的地址:" << p3 << "\nspeed: " << p3->speed << " acceleration: " << p3->acceleration << endl;delete p1;Car* p4 = new Car(420, 4.4);cout << "p4的地址:" << p4 << "\nspeed: " << p4->speed << " acceleration: " << p4->acceleration << endl;delete p2;delete p3;delete p4;Car::freepool(); // 释放内存池return 0;
}

如果内存池用完了,根据业务的需求,一般有三种方法:

  • 扩展内存
  • 直接向系统申请内存
  • 返回空地址

编译运行,效果应该如下(地址是你PC上开辟的内存地址,与我的不同):

内存池的起始地址:0x56366e051eb0
分配了第一块内存:0x56366e051eb1
~~ 类的构造 speed: 320 acceleration: 4.6 ~~
p1的地址:0x56366e051eb1
speed: 320 acceleration: 4.6
分配了第二块内存:0x56366e051eba
~~ 类的构造 speed: 220 acceleration: 4 ~~
p2的地址:0x56366e051eba
speed: 220 acceleration: 4
向系统申请到的内存地址:0x56366e052ee0
~~ 类的构造 speed: 520 acceleration: 2.9 ~~
p3的地址:0x56366e052ee0
speed: 520 acceleration: 2.9
~~ 调用析构 ~~
释放第一块内存!
分配了第一块内存:0x56366e051eb1
~~ 类的构造 speed: 420 acceleration: 4.4 ~~
p4的地址:0x56366e051eb1
speed: 420 acceleration: 4.4
~~ 调用析构 ~~
释放第二块内存!
~~ 调用析构 ~~
~~ 调用析构 ~~
释放第一块内存!
内存池已释放

感谢浏览,一起学习!

http://www.lryc.cn/news/492279.html

相关文章:

  • 如何使用PHP爬虫获取店铺详情:一篇详尽指南
  • HTML5和CSS3新增特性
  • linux运行vue编译后的项目
  • 论文阅读:A Software Platform for Manipulating theCamera Imaging Pipeline
  • 【MySQL篇】持久化和非持久化统计信息的深度剖析(第一篇,总共六篇)
  • Ubuntu下安装Qt
  • 丹摩征文活动 | AI创新之路,DAMODEL助你一臂之力GPU
  • 数据库(总结自小林coding)|索引失效的场景、慢查询、原因及如何优化?undo log、redo log、binlog 作用、MySQL和Redis的区别
  • Docker容器运行CentOS镜像,执行yum命令提示“Failed to set locale, defaulting to C.UTF-8”
  • OpenCV基本图像处理操作(六)——直方图与模版匹配
  • 【LLM学习笔记】第四篇:模型压缩方法——量化、剪枝、蒸馏、分解
  • python3 自动更新的缓存类
  • 英语知识网站开发:Spring Boot框架应用
  • 文件上传upload-labs-docker通关
  • git(Linux)
  • Doris实战—构建日志存储与分析平台
  • 【vue3+Typescript】unapp+stompsj模式下替代plus-websocket的封装模块
  • Tcon技术和Tconless技术介绍
  • C#-利用反射自动绑定请求标志类和具体执行命令类
  • 高中数学练习:初探均值换元法
  • 数据结构单链表,顺序表,广义表,多重链表,堆栈的学习
  • 【保姆级教程】使用lora微调LLM并在truthfulQA数据集评估(Part 2.在truthfulQA上评估LLM)
  • thinkphp中对请求封装
  • leetcode hot100【LeetCode 215.数组中的第K个最大元素】java实现
  • 簡單易懂:如何在Windows系統中修改IP地址?
  • Python中的23种设计模式:详细分类与总结
  • 日历使用及汉化——fullcalendar前端
  • 视频截断,使用 FFmpeg
  • 使用系统内NCCL环境重新编译Pytorch
  • 1. Klipper从安装到运行