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 …...

亚马逊云科技人工智能内容审核服务:大大降低生成不安全内容的风险
生成式人工智能技术发展日新月异,现在已经能够根据文本输入生成文本和图像。Stable Diffusion是一种文本转图像模型,可以创建栩栩如生的图像应用。通过Amazon SageMaker JumpStart,使用Stable Diffusion模型轻松地从文本生成图像。 尽管生成式…...

2023年高教社杯数学建模思路 - 案例:最短时间生产计划安排
文章目录 0 赛题思路1 模型描述2 实例2.1 问题描述2.2 数学模型2.2.1 模型流程2.2.2 符号约定2.2.3 求解模型 2.3 相关代码2.4 模型求解结果 建模资料 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 最短时…...

算法工程题(二叉树递归)
* 题意说明: * 给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。 * 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 * * 示例 1: * 输入:p [1,2,3]…...

“指针跃动”受邀参加全球贸易服务峰会
“指针跃动”受邀参加全球贸易服务峰会 有“服”同享 共赢未来 引子 在全球化日益盛行的今天,贸易不再仅仅是物质的交流,更涉及到服务、理念、文化和科技的共享。中国国际服务贸易交易会全球贸易服务峰会,就是这个趋势的集中体现。在这次峰会…...

Go Web开发的高级技巧和最佳实践
Go Web开发的高级技巧和最佳实践 欢迎来到Go语言Web开发的高级技巧和最佳实践指南。在这篇文章中,我们将深入探讨Go语言Web应用程序的高级主题,包括性能优化、安全性、部署和微服务架构。 性能优化 性能是Web应用程序的关键因素之一。Go语言以其出色的…...

Verilog 基础知识
1、数值种类 Verilog HDL 有下列四种基本的值来表示硬件电路中的电平逻辑: 0:逻辑 0 或 “假”1:逻辑 1 或 “真”x 或 X:未知 x 意味着信号数值的不确定,即在实际电路里,信号可能为 1,也可能…...

element ui 表格组件与分页组件的二次封装
目录 组件封装 parseTime函数 debounce 函数 页面使用 【扩展】vue 函数式组件 函数式组件特点: 函数式组件的优点: 【扩展】vue中的render函数 一、初步认识render函数 二、为什么使用render函数 三、render函数的解析 组件封装 这段代码是一…...

递归算法学习——有效的数独,解数独
一,有效的数独 1.题意 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#x…...

基于Alexnet深度学习网络的人员口罩识别算法matlab仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 file_path1 test\mask\;% 图像文件夹路径 %获取测试图像文件夹下所有jpg格式的图像文件…...

【Java Web】利用Spring整合Redis,配置RedisTemplate
1. 在config中加入RedisConfig配置类 package com.nowcoder.community.config;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFacto…...