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.mzph.cn/news/70680.shtml

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

相关文章

elasticsearch访问9200端口 提示需要登陆

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; elasticsearch访问9200端口 提示需要登陆 问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 在E:\elasticsearch-8.9.1-windows-x86_64\elasticsearch-8.9.1\bin目录下输入命令 ela…

python Playwright优化页面等待和处理异步操作

在使用 Playwright 进行页面自动化时&#xff0c;优化页面等待和处理异步操作是非常重要的&#xff0c;可以提高脚本的稳定性和执行效率。 优化页面等待和处理异步操作的建议 **1. 使用正确的等待条件&#xff1a;**Playwright 提供了多种等待条件&#xff0c;如等待元素出现…

【CSS】简记CSS效果:通过transition(动画过渡属性)实现侧边栏目滑入滑出

需求 在资金明细的页面中&#xff0c;点击按钮时筛选区域从左侧滑出&#xff0c;完成筛选点击确认后调用接口完成数据查询&#xff0c;筛选区域滑入左侧&#xff1b; 基于微信小程序页面实现 wxml代码 <view><!-- 操作按钮 --><button type"primary&qu…

昨天面试的时候被提问到的问题集合(答案)

1、vue的双向绑定原理是什么&#xff1f;里面的关键点在哪里&#xff1f; Vue的双向绑定原理是基于Object.defineProperty或者Proxy来实现的&#xff0c;其关键点在于数据劫持&#xff0c;即对数据的读取和修改进行拦截&#xff0c;在数据发生变化时自动更新视图 2、实现水平垂…

JVM调优记录

因为大量数据备份&#xff1b;导致在备份过程出现堆溢出的情况 当前情况 总内存&#xff1a;7.92G 已使用&#xff1a;3.7G jvm总内存最大&#xff1a;3.06G jvm非堆内存&#xff1a;最大1.23G&#xff0c;使用<170M jvm堆内存&#xff1a;最大1.83G 计算 如果预留2G扩展…

PSP - 蛋白质结构预测 OpenFold Multimer 重构训练模型的数据加载

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132602155 OpenFold Multimer 在训练过程的数据加载时&#xff0c;需要将 MSA 与 Template 信息转换成 Feature&#xff0c;再进行训练&#xff0…

[machineLearning]非监督学习unsupervised learning

1.什么是非监督学习 常见的神经网络是一种监督学习,监督学习的主要特征即为根据输入来对输出进行预测,最终会得到一个输出数值.而非监督学习的目的不在于输出,而是在于对读入的数据进行归类,选取特征,打标签,通过对于数据结构的分析来完成这些操作, 很少有最后的输出操作. 从…

ChatGPT集锦

目录 1. 一条指令让ChatGPT变的更强大2. 对ChatGPT提问时,常见的10种错误描述3. Custom instructions如何设置1. 一条指令让ChatGPT变的更强大 在使用GPT的过程中,如何让AI更清晰地了解你的需求很重要?今天分享一个指令,可以让GPT成为你的好同事,与你一起分析和解决问题,…

C# Winform 简单排期实现(DevExpress TreeList)

排期的需求在很多任务安排的系统中都有相应的需求&#xff0c;原生的Winform控件并未提供相应的控件&#xff0c;一般都是利用DataGridViewTreeView组合完成相应的需求&#xff0c;实现起来比较麻烦。用过DevExpress控件集的开发者应该知道&#xff0c;DevExpress WinForm提供了…

【动态规划刷题 10】等差数列划分 最长湍流子数组

413. 等差数列划分 链接: 413. 等差数列划分 如果一个数列 至少有三个元素 &#xff0c;并且任意两个相邻元素之差相同&#xff0c;则称该数列为等差数列。 例如&#xff0c;[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。 给你一个整数数组 nums &#xff0c;返回…

腾讯汤道生:超千亿参数 超2万亿tokens 腾讯混元大模型向行业全面开放

9月7日&#xff0c;2023腾讯全球数字生态大会在深圳宝安举行。腾讯集团高级执行副总裁、云与智慧产业事业群CEO汤道生表示&#xff0c;腾讯将迈入“全面拥抱大模型”时代&#xff1a;“以大模型生成技术为核心&#xff0c;人工智能正在成为下一轮数字化发展的关键动力&#xff…

stable diffusion实践操作-SD原理

系列文章目录 本文专门开一节写SD原理相关的内容&#xff0c;在看之前&#xff0c;可以同步关注&#xff1a; stable diffusion实践操作 文章目录 系列文章目录前言一、原理说明1.1、出图原理1.1.1 AI画画不是和人一样&#xff0c;从0开始&#xff0c;而是一个去噪点的过程&am…

华为云云服务器评测|详解 Nacos 安装部署

环境配置 服务器云耀云服务器L操作系统CentOS 7.9 64bit | 公共镜像JDK64 bit JDK 1.8MavenMaven 3.2.xnacos-server2.2.3 下载地址 官方githubRelease 2.2.3 (May 25th, 2023) alibaba/nacos GitHub百度网盘链接&#xff1a;https://pan.baidu.com/s/1K8UE6iJL2ZnosUY83b…

金鸣识别名片识别模块 ,名片扫描仪的神仙“伴侣”

名片扫描仪是现代办公中常见的设备&#xff0c;其作用是将纸质名片转换为电子格式并进行识别。在实现这一功能方面&#xff0c;使用自带OCR功能和金鸣识别两种方式均具有各自的优势。 一方面&#xff0c;自带OCR功能的名片扫描仪具有便捷性和即时性的优势。通过设备内置的OCR技…

删除单链表偶数节点

本题要求实现两个函数&#xff0c;分别将读入的数据存储为单链表、将链表中偶数值的结点删除。链表结点定义如下&#xff1a; struct ListNode { int data; struct ListNode *next; }; 函数接口定义&#xff1a; struct ListNode *createlist(); struct ListNode *deleteeven( …

pdf怎么转换成jpg图片?

随着数字文档的广泛应用&#xff0c;将PDF转换为JPG图片格式成为了一个常见的需求。无论是为了在网页上展示内容&#xff0c;还是为了与他人分享图片&#xff0c;以下是一些简单的方法&#xff0c;帮助您将PDF文件快速转换为高质量的JPG图片。 方法一&#xff1a;在线PDF转JPG…

手写Mybatis:第12章-完善ORM框架,增删改查操作

文章目录 一、目标&#xff1a;完善增删改查二、设计&#xff1a;完善增删改查三、实现&#xff1a;完善增删改查3.1 工程结构3.2 完善增删改查类图3.3 扩展解析元素3.4 新增执行方法3.4.1 执行器接口添加update3.4.2 执行器抽象基类3.4.3 简单执行器 3.5 语句处理器实现3.5.1 …

Nginx - 根据请求参数路由进行不同的响应

文章目录 需求思路 需求 业务有一个统一入口 /api/biz?type1 /api/biz/type2需要对不同的接口实现流控 最常见的是通过location进行路径匹配的时候&#xff0c;但是无法使用正则表达一起捕获这个路径和querstring的参数。如果我们想通过URL里面的Query String进行不同的rew…

ChatGPT 插件 “Consensus“ 实现论文搜索功能;数据工程在语言建模中的重要性

&#x1f989; AI新闻 &#x1f680; ChatGPT 插件 “Consensus” 实现论文搜索功能 摘要&#xff1a;OpenAI 推出了一个名为 “Consensus” 的插件&#xff0c;可在 ChatGPT 上进行论文搜索。用户只需用一句话描述自己想了解的问题&#xff0c;插件就能从 2 亿篇论文中搜索并…

使用Puppeteer爬取地图上的用户评价和评论

导语 在互联网时代&#xff0c;获取用户的反馈和意见是非常重要的&#xff0c;它可以帮助我们了解用户的需求和喜好&#xff0c;提高我们的产品和服务质量。有时候&#xff0c;我们需要从地图上爬取用户对某些地点或商家的评价和评论&#xff0c;这样我们就可以分析用户对不同…