C++内存管理(2)new、delete详解
目录
new operator(new操作)
new类对象时加不加括号的差别
new工作任务
delete工作任务
new和delete 堆区空间操作(对比malloc和free)
new和delete操作基本类型的空间
new和delete操作基本类型的数组
new和delete操作类的空间
new和delete操作对象数组
new内存分配细节探秘
为什么要尽可能少的调用malloc?
new和delete的重载
为什么要重载 new
监测内存创建销毁,统计和监控泄漏
内存对齐的处理
特定应用:多进程内存共享
重载全局的 new 和 delete
在全局new和delete中添加定制行为
重载类的操作符 new 和 delete
类new和delete操作符重载基础
对齐的内存分配
共享内存的分配
定位new(placement new)
功能
使用placement new
placement new对象的销毁
placement new的应用
硬件编程
实现基础库
多种版本的operator new重载
new operator(new操作)
new类对象时加不加括号的差别
- A *pa = new A;//有无构造函数初始化为垃圾值
- A *pa2 = new A();//无构造函数初始化为0,有构造函数为垃圾值
在g++中默认初始化成员变量为0,而A *pa2 = new A(5)初始化成员变量为5
#include <iostream>
using namespace std;class A
{
public:int m_num;public:A(){};A(int num) : m_num(num){cout << "construct" << endl;}~A(){cout << "disconstruct" << endl;};
};int main(int argc, char const *argv[])
{A *pa = new A;A *pa2 = new A();A *pa3 = new A(5);cout << "pa->m_num = " << pa->m_num << endl;cout << "pa2->m_num = " << pa2->m_num << endl;cout << "pa3->m_num = " << pa3->m_num << endl;delete pa;delete pa2;delete pa3;return 0;
}
运行结果:
new工作任务
调用operator new()--malloc
调用了分配对象的构造函数
delete工作任务
调用了分配对象的析构函数
调用operator delete()--free
new和delete 堆区空间操作(对比malloc和free)
new和delete操作基本类型的空间
new和malloc delete和free 没有区别
区别:
new 不用强制类型转换
new在申请空间的时候可以 初始化空间内容
new和delete操作基本类型的数组
new和delete操作类的空间
malloc不会调用构造函数 free不会调用析构函数
new 会调用构造函数 delete调用析构函数
new调用有参构造
new和delete操作对象数组
new内存分配细节探秘
- new分配内存实际是调用malloc函数进行内存分配;
- 思考:delete/free是如何知道要释放多大的内存?
- 分配内存时,为了记录和管理分配出去的内存,额外多分配了不少内存,造成了浪费;尤其是你频繁的申请小块内存时,造成的浪费更明显,更严重
- 实际分配情况
为什么要尽可能少的调用malloc?
- 内存开销: 每次调用 malloc 都会引入额外的内存开销,包括内存分配表、堆管理等数据结构,这些开销可能会在大量小型分配时累积并消耗大量内存。
- 内存泄漏风险: 使用 malloc 分配内存后,需要负责在不再使用内存时释放它。如果你频繁地调用 malloc,则需要管理和追踪许多不同的内存分配,容易出现内存泄漏问题,导致程序在运行时逐渐耗尽内存。
- 性能开销: 内存分配和释放是相对较慢的操作,涉及到内部数据结构的维护、内存搜索等操作。频繁调用 malloc 可能会导致性能下降,特别是在大规模数据处理或高性能计算应用中。
- 碎片化: 频繁分配和释放小块内存可能导致内存碎片化,即使系统总内存充足,也可能由于碎片化问题无法满足大块内存分配的需求。
为了减少 malloc 调用的次数,可以考虑以下方法:
- 使用栈内存: 对于小型临时变量,可以使用栈内存而不是堆内存,因为栈内存的分配和释放非常快速。但要注意栈内存的生命周期通常较短。
- 池化: 如果需要频繁创建和销毁对象,可以使用内存池技术,通过一次性分配一大块内存并自行管理对象的分配和释放。
- 缓存: 对于某些可复用对象,可以使用缓存来避免频繁分配和释放内存。这在对象池等场景中很有用。
- 避免不必要的动态分配: 如果可以在编译时确定数组或数据结构的大小,可以使用栈数组或静态分配来避免动态分配。
new和delete的重载
为什么要重载 new
监测内存创建销毁,统计和监控泄漏
在C++中,内存管理是开发者的一项重要责任,也是容易出错的地方。开发者可能会遗忘释放已分配的内存,导致内存泄漏。重载new和delete可以帮助开发者更好地追踪和管理内存分配。通过在重载的new和delete操作符中插入日志或者调试语句,开发者可以监测和记录所有内存分配和释放的情况,从而检测内存泄漏。
例如,以下的代码展示了如何重载new和delete操作符来监测和追踪内存分配:
void* operator new(size_t size) {void* p = malloc(size);std::cout << "Allocated " << size << " bytes at address " << p << std::endl;return p;
}void operator delete(void* p) {std::cout << "Deallocated memory at address " << p << std::endl;free(p);
}
内存对齐的处理
在一些硬件平台和操作系统上,为了实现最优性能,数据需要按照某种特定的边界对齐。如果没有对齐,可能会导致性能下降,甚至运行错误。通过重载new和delete,我们可以为特定的类实现定制的内存对齐方式。
下面的代码演示了如何重载new和delete操作符来实现内存对齐:
class Aligned {
public:static void* operator new(std::size_t size) {void* p = std::aligned_alloc(alignof(Aligned), size);if (!p) {throw std::bad_alloc();}return p;}static void operator delete(void* p) {std::free(p);}
};
特定应用:多进程内存共享
在某些情况下,多个进程可能需要访问同一块内存区域。在这种情况下,可以通过重载new和delete操作符,实现在共享内存区域中分配和释放对象。
例如,以下的代码展示了如何通过重载new和delete来在共享内存中分配和释放对象:
// 假设SharedMemoryManager是一个用于管理共享内存的类
class SharedMemoryManager {
public:void* allocate(size_t size);void deallocate(void* p);
};class SharedMemoryObject {
public:void* operator new(size_t size) {return SharedMemoryManager::allocate(size);}void operator delete(void* p) {SharedMemoryManager::deallocate(p);}
};
在以上的例子中,SharedMemoryObject类的对象将会被分配在共享内存中,从而可以被多个进程访问。
重载全局的 new 和 delete
全局的new和delete操作符可被重载以满足特定的需求,比如定制内存管理策略,或者为内存分配和释放添加自定义行为。要注意,这些全局重载将影响到整个程序的范围,包括标准库的容器等,所以在实践中应谨慎使用。
void* operator new(size_t size) {// ... 实现代码
}void operator delete(void* p) {// ... 实现代码
}
operator new需要返回一个足够大,可以容纳请求内存大小的指针。如果内存分配失败,需要抛出std::bad_alloc异常。operator delete需要释放传入的指针指向的内存。
在全局new和delete中添加定制行为
下面的代码将在全局的new和delete操作符中添加一些定制的行为。在分配和释放内存时,我们会打印一些信息到控制台,以便于跟踪内存的使用情况:
#include <cstdlib>
#include <iostream>void* operator new(size_t size) {void* p = std::malloc(size);if (!p) {throw std::bad_alloc();}std::cout << "Allocated " << size << " bytes at address " << p << std::endl;return p;
}void operator delete(void* p) {std::cout << "Deallocated memory at address " << p << std::endl;std::free(p);
}
以上代码演示了如何在全局的new和delete操作符中添加自定义的行为。这种方式在实际开发中可以帮助我们更好地理解和跟踪内存的使用情况。不过请注意,添加过多的日志可能会对性能产生影响,所以在生产环境中通常不会添加过多的日志信息。
重载类的操作符 new 和 delete
对类的new和delete操作符进行重载允许我们为该类的对象提供定制的内存管理策略。这对于需要进行特殊内存管理的类来说特别有用,例如需要在共享内存中创建的对象,或者需要进行特殊对齐的对象。
类new和delete操作符重载基础
对类的new和delete操作符进行重载的基本形式如下:
class MyClass {
public:static void* operator new(std::size_t size);static void operator delete(void* p);
};
operator new需要返回一个足够大,可以容纳请求内存大小的指针。如果内存分配失败,需要抛出std::bad_alloc异常。operator delete需要释放传入的指针指向的内存。
对齐的内存分配
假设我们有一个需要8字节对齐的类,我们可以通过重载new和delete操作符来满足这个要求:
#include <cstdlib>
#include <new>class Aligned {
public:static void* operator new(std::size_t size) {void* p = std::aligned_alloc(8, size);if (!p) {throw std::bad_alloc();}return p;}static void operator delete(void* p) {std::free(p);}
};
在这个例子中,我们使用了std::aligned_alloc函数来进行对齐的内存分配。如果分配失败,我们抛出std::bad_alloc异常。在operator delete中,我们简单地调用std::free来释放内存。
共享内存的分配
假设我们有一个需要在共享内存中创建的对象,我们可以通过重载new和delete操作符来实现:
// 假设SharedMemoryManager是一个用于管理共享内存的类
class SharedMemoryManager {
public:static void* allocate(std::size_t size);static void deallocate(void* p);
};class SharedMemoryObject {
public:static void* operator new(std::size_t size) {return SharedMemoryManager::allocate(size);}static void operator delete(void* p) {SharedMemoryManager::deallocate(p);}
};
在这个例子中,SharedMemoryObject类的对象将会在共享内存中创建和销毁。这允许我们在多个进程间共享这些对象。
定位new(placement new)
放置new (placement new) 是一个特殊版本的new操作符,它允许程序员将对象创建在已经分配的内存上。换句话说,它允许我们"放置"一个新的对象在我们指定的、已经存在的内存位置上。
功能
在已经分配的原始内存中初始化一个对象;
- 已经分配,定位new并不分配内存,你需要提前将这个定位new要使用的内存分配出来
- 初始化一个对象(初始化一个对象的内存),调用这个对象的构造函数不再分配内存;
使用placement new
在普通的new操作中,首先会申请一块足够的内存,然后在这块内存上构造对象。但是在placement new中,内存必须已经存在,它只负责在指定的内存上构造对象。以下是一个使用placement new的例子:
#include <new> // 需要包含这个头文件来使用placement newchar buffer[1024]; // 预分配的内存int* p = new (buffer) int(123); // 在buffer上放置一个int对象
对于类,placement new最好在我们需要使用的类中重载,否则在类外重载会影响到其它类型分配空间
#include <iostream>
using namespace std;void *operator new(size_t size)
{void *p = malloc(size);std::cout << "Allocated " << size << " bytes at address " << p << std::endl;return p;
}void operator delete(void *p)
{std::cout << "Deallocated memory at address " << p << std::endl;free(p);
}class A
{
public:int m_num;public:A(){cout<<"default construct"<<endl;};A(int num) : m_num(num){cout << "construct" << endl;}~A(){cout << "disconstruct" << endl;};void *operator new(size_t size, void *p){cout << "placement new" << endl;return p;}
};int main(int argc, char const *argv[])
{void *p = (void *)new char[sizeof(A)];A *pa = new (p) A();pa->m_num = 5;cout << *((int *)p) << endl;delete pa;return 0;
}
placement new对象的销毁
由于placement new仅仅在已经分配的内存上创建对象,而不会分配内存,所以当不再需要这个对象时,我们需要手动调用该对象的析构函数来销毁对象:
p->~int(); // 手动调用析构函数
需要注意的是,这里只销毁了对象,但并没有释放内存。内存的释放需要根据实际的情况来处理。例如,如果这块内存是在栈上分配的,那么当退出作用域时会自动释放;如果是在堆上分配的,那么可能需要手动释放。
placement new的应用
placement new的一个主要应用是当我们需要在特定的位置创建对象时,比如在已分配的堆内存上,或者在栈内存上,甚至在硬件指定的特定内存地址上。
此外,placement new也常用于实现自定义的内存池,内存池可以减少动态分配和释放内存带来的开销。我们可以预先分配一大块内存,然后通过placement new在这块内存上创建对象,从而提高内存使用的效率。
硬件编程
如果知道了硬件设备的地址,想要将那个硬件设备与一个C++类直接关联,那么定位new就非常有效了
通过将placement new可以将C++的类之间关联到硬件设备上,操作该对象就相当于操作硬件
如下面程序所示,假如操作STM32的GPIOB->GPIO_Pin1,假设GPIO_Pin1的存储器映射地址为0x00005600。由于类A的对象pa的地址就是对象pa内首个字段m_num的地址,因此操作m_num就相当于操作地址0x00005600。
#include <iostream>
using namespace std;class A
{
public:int m_num;public:A(){};A(int num) : m_num(num){cout << "construct" << endl;}~A(){cout << "disconstruct" << endl;};
};int main(int argc, char const *argv[])
{//访问硬件:将C++的类之间关联到硬件设备上,操作该对象就相当于操作硬件//单片机/STM32/ARM9:操作硬件的物理地址就相等于操作该硬件//GPIOB->GPIO_Pin1void *p = (void*)0x00005600;A *pa = new(p) A();pa->m_num = 1;//拉高电平pa->m_num = 0;//拉低电平return 0;
}
实现基础库
基础库一般为了效率要先预分配内存,然后在预分配的内存上执行构造,几乎所有的C++容器都用到了定位new
多种版本的operator new重载
优先级:内部new、全局new
可以重载很多版本的operator new,只要每个版本参数不同就行,但是第一个参数是固定的,都是size_t,表示要new对象的sizeof值
相关文章:

C++内存管理(2)new、delete详解
目录 new operator(new操作) new类对象时加不加括号的差别 new工作任务 delete工作任务 new和delete 堆区空间操作(对比malloc和free) new和delete操作基本类型的空间 new和delete操作基本类型的数组 new和delete操作类的…...

ELK集群搭建流程(实践可用)
一、概述 ELK 是一个由三个开源软件工具组成的数据处理和可视化平台,包括 Elasticsearch、Logstash 和 Kibana。这些工具都是由 Elastic 公司创建和维护的。 Elasticsearch 是一个分布式的搜索和分析引擎,可以将大量数据存储在一个或多个节点上…...
react-quill富文本 中文输入法触发change问题
使用的富文本是编辑器 react-quill 需求: 点击按钮插入自定义颜色文字,然后手动输入为正常颜色。 问题: quill组件把带颜色的字体创建个dom, 临近的文字都会整合进一个dom中,导致输入的文字和插入的带颜色 都统一成一个颜色了…...

Upload-labs 1~15 通关详细教程
文章目录 Upload-labs 1~15 通关详细教程Pass-01-前端js验证Pass-02-后端MIME验证Pass-03-黑名单验证Pass-04-黑名单验证.htaccessPass-05-文件后缀名大小写绕过Pass-06-文件后缀名空格绕过Pass-07-文件后缀名点绕过Pass-08-文件后缀名::$DATA绕过Pass-09-点空格点空格绕过Pass…...
ChatGPT分析日本排放核污水对世界的影响
文章目录 1 背景2 环境影响3 健康影响4 国际关系影响5 应对措施 近段时间被日本排放核污水到海里的消息刷屏了,这一举措引发了广泛的关注和担忧。本文结合ChatGPT来分析这件事的前因后果、会对世界造成的影响、以及应对措施。 1 背景 受2011年发生的大地震及海啸影响…...

eclipse进入断点之后,一直卡死,线程一直在运行【记录一种情况】
问题描述: 一直卡死在某个断点处,取消断点也是卡死在这边的进程处。 解决方式: 将JDK的使用内存进行了修改 ① 打开eclipse,window->preference->Java->Installed JREs,选中使用的jdk然后点击右侧的edit,在…...
2.5 动态字符串 String (完整源码)
C自学精简教程 目录(必读) C数据结构与算法实现(目录) 本文的实现基本上和 动态数组 vector 是一样的。 因为大部分接口都一样。 所以,本文就直接给出全部的源码和运行结果。 //------下面的代码是用来测试你的代码有没有问题的辅助代码…...

Ansible之变量
一)Ansible变量介绍 我们在PlayBook⼀节中,将PlayBook类⽐成了Linux中的shell。 那么它作为⼀⻔Ansible特殊的语⾔,肯定要涉及到变量定义、控 制结构的使⽤等特性。 在这⼀节中主要讨论变量的定义和使⽤ 二)变量命名规则 变量的…...

自动化测试面试常见技术题目
1:一行代码实现1--100之和 print(sum(list(range(1,101)))) 2:如何在一个函数内部修改全局变量 global 修改全局变量 局部作用域只能调用全局作用域的变量,但是不熊修改全局作用域的变量,如果想要修改全局作用域的变量需要gl…...

aarch64 arm64 部署 stable diffusion webui 笔记 【2】继续安装其他依赖 gfpgan
接上篇 aarch64 arm64 部署 stable diffusion webui 笔记 【1】准备 venv 安装pytorch 验证cuda_hkNaruto的博客-CSDN博客 编辑requirements_versions.txt,注释掉torch 启动webui.sh (venv) [rootceph3 stable-diffusion-webui]# useradd yeqiang useradd…...

使用ECS和RDS部署WordPress,搭建个人博客并使用域名访问
目录 一、准备工作 1、准备ECS服务器 2、创建数据库账号和密码 二、部署环境 1、远程连接 2、安装Apache服务 3、部署WordPress 三、对博客的优化并使用域名访问 1、博客的设计优化 1.1 插件的使用 1.2 博客的设计介绍 2、使用域名访问 四、个人博客部署的心得 1…...

C# Winform 简单排期实现(DevExpress TreeList)
排期的需求在很多任务安排的系统中都有相应的需求,原生的Winform控件并未提供相应的控件,一般都是利用DataGridViewTreeView组合完成相应的需求,实现起来比较麻烦。用过DevExpress控件集的开发者应该知道,DevExpress WinForm提供了…...
2023高教社杯国赛数学建模C题思路+模型+代码(9.7晚开赛后第一时间更新)
目录 1.C题思路模型:9.7晚上比赛开始后,第一时间更新,获取见文末名片 2.竞赛注意事项:包括比赛流程,任务分配,时间把控,论文润色,已经发布在文末名片中 3.常用国赛数学建模算法 …...

QT6中添加串口模块SerialPort最简单方法
qt6.2.3以上版本已经开始支持SerialPort包了,不用在傻傻的自己去编译包了。 在安装的时候勾选SerialPort即可。 等着安装完即可。 如果已经安装完了的小伙伴,可以用 从新打开维护 选择增加或者删除组件 即可从新选择组件...

LeetCode每日一题:1123. 最深叶节点的最近公共祖先(2023.9.6 C++)
目录 1123. 最深叶节点的最近公共祖先 题目描述: 实现代码与解析: dfs 原理思路: 1123. 最深叶节点的最近公共祖先 题目描述: 给你一个有根节点 root 的二叉树,返回它 最深的叶节点的最近公共祖先 。 回想一下&…...
Oracle查看锁表和正在执行的Sql
查看当前被锁的表(需要有管理员权限): --查看锁表进程SQL语句1: select sess.sid,sess.serial#,lo.oracle_username,lo.os_user_name,ao.object_name,lo.locked_modefrom v$locked_object lo, dba_objects ao, v$session sesswh…...
Linux centos 卸载 ceph
在CentOS上卸载Ceph的操作步骤: 1. 停止Ceph集群:首先,你需要停止Ceph集群中的所有服务。在每个节点上运行以下命令来停止所有服务 systemctl stop ceph.target 2. 卸载Ceph软件包:在每个节点上,使用yum包管理器卸载C…...
ElementUI浅尝辄止34:Radio 单选框
在一组备选项中进行单选 1.如何使用? 由于选项默认可见,不宜过多,若选项过多,建议使用 Select 选择器。 //要使用 Radio 组件,只需要设置v-model绑定变量,选中意味着变量的值为相应 Radio label属性的值&…...

开始MySQL之路——MySQL三大日志(binlog、redo log和undo log)概述详解
前言 MySQL实现事务、崩溃恢复、集群的主从复制,底层都离不开日志,所以日志是MySQL的精华所在。只有了解MySQL日志,才算是彻底搞懂MySQL。 日志是mysql数据库的重要组成部分,记录着数据库运行期间各种状态信息。mysql日志主要包…...
router基础使用
1.安装router npm i vue-router3 安装后 2.写出路由界面 接着 3.配置路由 import Vue from vue import VueRouter from vue-router import Home from "../views/Home.vue" import About from "../views/About.vue" Vue.use(VueRouter)const routes …...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...

均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...