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值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/122884.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt开发_调用OpenCV(3.4.7)设计完成人脸检测系统

一、前言 近年来,人脸识别技术得到了广泛的应用,它可以在各种场景中实现自动化的人脸检测和识别,例如安防监控、人脸解锁、人脸支付等。 该项目的目标是设计一个简单易用但功能强大的人脸检测系统,可以实时从摄像头采集视频,并对视频中的人脸进行准确的检测和框选。通过…

0012Java程序设计-springboot基于微信小程序的校园智慧帮系统的设计与实现

摘要目录相关技术2.1 MySQL数据库2.2 SpringBoot框架2.3 uniapp框架2.4 B/S架构 系统设计系统实现开发环境 摘要 随着移动互联网高速发展&#xff0c;手机、移动智能终端设备在生活中有着越来越重要的地位。在高校推崇以人为本的今天&#xff0c;也逐渐重视“移动互联网”技术…

​7.1 项目1 学生通讯录管理:文本文件增删改查(C++版本)(自顶向下设计+断点调试) (A)​

C自学精简教程 目录(必读) 作业目标&#xff1a; 这个作业中&#xff0c;你需要综合运用之前文章中的知识&#xff0c;来解决一个相对完整的应用程序。 作业描述&#xff1a; 1 在这个作业中你需要在文本文件中存储学生通讯录的信息&#xff0c;并在程序启动的时候加载这些…

【Mycat1.6】缓存不生效问题处理

背景 系统做读写分离&#xff0c;有大量读需求&#xff0c;基本没有实时获取数据业务需要&#xff0c;所以可以启用缓存来减缓数据库压力&#xff0c;传统使用mybatis的缓存需要大量侵入式声明&#xff0c;所以结合需求使用Mycat中间件来满足 数据库结构 mysql-master&#…

渗透测试漏洞原理之---【业务安全】

文章目录 1、业务安全概述1.1业务安全现状1.1.1、业务逻辑漏洞1.1.2、黑客攻击目标 2、业务安全测试2.1、业务安全测试流程2.1.1、测试准备2.1.2、业务调研2.1.3、业务建模2.1.4、业务流程梳理2.1.5、业务风险点识别2.1.6 开展测试2.1.7 撰写报告 3、业务安全经典场景3.1、业务…

多维时序 | MATLAB实现GWO-BiLSTM灰狼算法优化双向长短期记忆神经网络的多变量时间序列预测

多维时序 | MATLAB实现GWO-BiLSTM灰狼算法优化双向长短期记忆神经网络的多变量时间序列预测 目录 多维时序 | MATLAB实现GWO-BiLSTM灰狼算法优化双向长短期记忆神经网络的多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现CNN-BiGRU-A…

手写Spring:第11章-容器事件和事件监听器

文章目录 一、目标&#xff1a;容器事件和事件监听器二、设计&#xff1a;容器事件和事件监听器三、实现&#xff1a;容器事件和事件监听器3.1 工程结构3.2 容器事件和事件监听器类图3.3 定义和实现事件3.3.1 定义事件抽象类3.3.2 定义应用上下文事件实现类3.3.3 上下文刷新事件…

如何将枯燥的大数据进行可视化处理?

在数字时代&#xff0c;大数据已经成为商业、科学、政府和日常生活中不可或缺的一部分。然而&#xff0c;大数据本身往往是枯燥的、难以理解的数字和文字&#xff0c;如果没有有效的方式将其可视化&#xff0c;就会错失其中的宝贵信息。以下是一些方法&#xff0c;可以将枯燥的…

基于SpringBoot的汽车租赁系统

基于SpringBootVue的汽车租赁系统&#xff0c;前后端分离 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 角色&#xff1a;管理员、业务员、用户 管理员 用户管理…

STM32 软件IIC 控制OLED 显示屏

1. 硬件IIC 实在是太难用了&#xff0c;各种卡死&#xff0c;各种发不出来数据&#xff0c;没那么多时间折腾了&#xff0c;还是用软件IIC 先吧&#xff0c;初始化 void OLED_Software_IIC_Init(void) {GPIO_InitTypeDef GPIO_InitStruct;RCC_AHBPeriphClockCmd(OLED_SOFTWARE…

Jmeter进阶使用指南-使用断言

Apache JMeter是一个流行的开源负载和性能测试工具。在JMeter中&#xff0c;断言&#xff08;Assertions&#xff09;是用来验证响应数据是否符合预期的一个重要组件。它是对请求响应的一种检查&#xff0c;如果响应不符合预期&#xff0c;那么断言会标记为失败。 以下是如何在…

Linux如何安装MySQL

Linux安装MySQL5.7 1、下载 官网下载地址&#xff1a;http://dev.mysql.com/downloads/mysql/ 2、复制下面几个文件 3、检查当前系统是否安装过mysql、检查当前mysql依赖环境、检查/tmp文件夹权限 1&#xff09;检查当前系统是否安装过mysql&#xff0c;执行安装命令前&am…

LabVIEW应用开发——LabVIEW2019保姆级介绍、安装、第一个程序

一、前言 LabVIEW是一种程序开发环境&#xff0c;由美国国家仪器&#xff08;NI&#xff09;公司研制开发&#xff0c;类似于C和BASIC开发环境&#xff0c;但是LabVIEW与其他计算机语言的显著区别是&#xff1a;其他计算机语言都是采用基于文本的语言产生代码&#xff0c;而Lab…

MyBatis配置及单表操作

文章目录 一. MyBatis概述二. MyBatis项目的创建1. 准备一个数据表2. 创建项目 三. MyBatis的使用1. 基本使用2. SpringBoot单元测试 四. 使用MyBatis实现单表操作1. 查询2. 修改3. 删除4. 新增 五. 基于注解完成SQL 一. MyBatis概述 MyBatis 是一款优秀的持久层框架&#xff…

Ubutnu允许ssh连接使用root与密码登录

文章目录 1. 修改sshd_config2. 设置root密码3. 重启SSH服务 1. 修改sshd_config 修改/etc/ssh/sshd_config文件&#xff0c;找到 #Authentication&#xff0c;将 PermitRootLogin 参数修改为 yes。如果 PermitRootLogin 参数被注释&#xff0c;请去掉首行的注释符号&#xff…

GitHubGiteeGitlab极狐(JihuLab)配置SSH公私钥详细过程

GitHub-微软-github.com Gitee-开源中国- gitee.com Gitlab-乌克兰GitLab 公司-gitlab.com 极狐(JihuLab)-中国代理商运营的Gitlab -gitlab.cn或者jihulab.com 一、生成SSH公钥和私钥 1.1 取消全局设置 $ git config --global user.name "你的名字" $ git confi…

创建10个线程并发执行(STL/Windows/Linux)

C并发编程入门 目录 STL 写法 #include <thread> #include <iostream> using namespace std;void thread_fun(int arg) {cout << "one STL thread " << arg << " !" << endl; }int main(void) {int thread_count 1…

线性代数的学习和整理19,特征值,特征向量,以及引入的正交化矩阵概念

目录 1 什么是特征值和特征向量&#xff1f; 1.1 特征值和特征向量这2个概念先放后 1.2 直观定义 1.3 严格定义 2 如何求特征值和特征向量 2.1 方法1&#xff1a;结合图形看&#xff0c;直观方法求 2.1.1 单位矩阵的特征值和特征向量 2.1.2 旋转矩阵 2.2 根据严格定义…

Typroa+PicGo验证图片上传选项失败原因:测试图片被封禁

使用环境 Windows 11PicGo 2.3.1Typora 1.2.5图床 sm.ms 问题描述 在搭建好环境之后&#xff0c;在Typroa里面验证图片上传选项&#xff0c;显示失败。 查看日志 2023-09-06 09:51:23 [PicGo INFO] [PicGo Server] is listening at 36677 2023-09-06 09:51:28 [PicGo INF…

OpenCV之ellipse函数

ellipse函数用来在图片中绘制椭圆、扇形&#xff0c;有两个重载函数。 函数原型1&#xff1a; void cv::ellipse( InputOutputArray img,Point center,Size axes,double angle,double startAngle,double …