1. 引言

在C++的世界里,动态内存管理是一个核心话题。对于从C语言过渡到C++的开发者来说,一个常见的困惑是:既然C语言的mallocfree依然可以在C++中使用,为什么C++还要引入newdelete这两个操作符?

本文将深入探讨这两对内存管理机制的根本区别,帮助你理解为何在C++中更推荐使用newdelete

2、本质区别:运算符vs函数

这就最根本的区别,决定了他们的行为与能力。

new和delete:是c++内置的运算符(operator),编译器直接理解它们的含义,并腐恶转换为底层的内存分配和对象构造调用。

malloc和free:是c语言标准库中定义的库函数,位于<stdlib>,<stdlib.h>头文件中,它们的功能由运行时库提供。

特性new/deletemalloc/free
本质c++操作符c库函数
内存分配在堆上分配指定类型的内存在堆上分配指定字节数的内存
析构函数/构造函数会调用不会调用
返回值返回明确类型的指针返回void*,需手动强制转换
分配失败抛异常返回NULL
重载可以进行类成员重载或全局重载不可以重载
计算大小编译器自动计算所需内存大小需手动使用sizeof计算字节数
初始化支持显式初始化只能分配未初始化的内存

3、类型安全与返回值:

malloc的返回值是void*,所以在使用的时候必须使用强制转换,如果忘记进行强制类型转换,那么就会发生错误,这也是malloc函数的弊端或者说是缺点之一。

// C style (类型不安全)
int *p = (int*)malloc(sizeof(int)); // 必须进行强制类型转换
*p = 10;
free(p);

new 直接返回所需类型的指针,是类型安全的。

// C++ style (类型安全)
int *p = new int; // 直接返回 int* 类型
*p = 10;
delete p;

接着往下看,这也是malloc与free函数与new和delete运算符的本质区别:

#include<iostream>
#include<stdlib.h>
using namespace std;
class Myclass
{
public:Myclass(){cout << "成功调用构造函数:Myclass()函数" << endl;}~Myclass(){cout << "成功调用析构函数:~Myclass()函数" << endl;}
};
int main()
{cout << "using malloc/free:" << endl;Myclass* obj1 = (Myclass*)malloc(sizeof(Myclass));free(obj1);//析构函数并不会被调用cout << "using new/delete:" << endl;Myclass* obj2 = new Myclass;delete obj2;//1、调用Myclass的析构函数,对于自定义类型,会调用它的析构函数//2、释放内存,将对象本身的内存还给操作系统。
}

从输出结构可以清晰地看到,malloc仅仅分配了足够大的内存空间,而new在分配内存后,还调用了类的构造函数来对实例化出的对象进行初始化操作,delete在释放内存前也会调用析构函数来清理资源(如关闭文件、释放内存),而free则直接释放内存,有可能导致内存泄漏,所以在c++里,十分建议写new和delete,不仅仅安全还很方便,malloc与free有的,new和delete都有,malloc与free没有的,new和delete也有。

4 、对于数组的处理:

使用free不需要关心是否是数组,但是如果你使用new[ ]进行内存分配,那么必须使用delete[ ]来释放对应的内存,去掉这个[ ]可能会报错!

接着看下面的代码:

#include<iostream>
#include<stdlib.h>
using namespace std;
class Myclass
{
public:Myclass(){cout << "成功调用构造函数:Myclass()函数" << endl;}~Myclass(){cout << "成功调用析构函数:~Myclass()函数" << endl;}
};
int main()
{Myclass* ptr = new Myclass[10];delete [] ptr;
}

在c++中,使用new[ ]动态分配数组时,对于内置类型,比如int,char,float,等等,new int[10]不会调用其构造函数,因为内置类型没有用户定义的构造函数,但是对于自定义类型而言,使用new[ ]来定义数组,必须我这里自定义的类型Myclass,这里的new Myclass[10]会调用10次Myclass类的构造函数和析构函数。

下面介绍的才是这篇博客的核心内容,也是本人从浅至深的一个过程。

5、operator  new和operator delete:

提到new和delete,那么就不得不谈到operator new和operator delete,那么operator new和operator delete有什么联系或者说是区别呢?

为了方便我把代码直接截图下来,当我们写出一句 Myclass* ptr=new Myclass[10]的时候,编译器会将其分解为三个步骤:

1、分配内存:调用operator new(sizeof(Myclass)函数(这一点我等下会通过观察汇编来进行验证),申请一块足够大的,并且未初始化的原始内存。

2、构造对象:

在上述内存地址上调用Myclass::myclass()构造函数,初始化这块内存,使其成为一个真正的对象。

3、返回指针:返回构造好的对象的地址,所以用指向这个类的指针来接收也就是Myclass*。

那么同理,delete obj也做了两件事情:

1、析构对象:调用obj->~Myclass()析构函数,清理对象占用的内存

2、释放资源:调用operator delete(obj)函数,释放对象所占用的原始内存块。

不仅仅如此,operator new与operator delete与new和delete:

new和delete是操作符,而operator new和operator delete是函数,换句说,operator new和operator delete就是C语言中malloc函数与free函数的加强版,它们不仅仅会开辟空间,还会在开辟空间的基础上进行抛异常。而opertaor new和operator delete底层上也是调用了malloc函数与free函数。

下面我将通过汇编来验证,new会通过调用operator new来开辟空间。

operator new 和 operator delete 做了什么?(单一职责)

这两个函数只负责第一步和最后一步,即原始内存的分配与释放。它们和 malloc/free 是同一级别的概念,但属于C++的体系。

void* operator new(size_t size)

它的唯一任务就是接受一个字节数 size,找到一块足够大的连续内存空间,并返回指向这块内存的 void* 指针。如果失败,它默认抛出 std::bad_alloc 异常。

void operator delete(void* ptr)

它的唯一任务是接受一个由 operator new 返回的 void* 指针,释放这块内存。

标准库已经提供了默认的全局 operator new 和 operator delete,它们通常就是基于 malloc 和 free 实现的。

你可以把它们想象成是C++世界里“高级的”、“会抛异常的” malloc 和 free

6、 重载 (Overloading)

这才是重载的意义所在。我们可以提供我们自己版本的 operator new 和 operator delete 函数,来接管内存分配和释放的过程。

第一种方式:

全局重载:

这种方式非常不推荐,因为程序中所有的new和delete都会调用我们自己写的版本,这么做可以说是不安全的一种行为。

#include <iostream>
#include <stdlib.h> // for malloc, free
using namespace std;// 全局重载 operator new
void* operator new(size_t size) {cout << "Global new called, size: " << size << endl;void* p = malloc(size);if (!p) throw bad_alloc(); // 遵循规范,分配失败抛异常return p;
}// 全局重载 operator delete
void operator delete(void* p) noexcept {cout << "Global delete called" <<endl;free(p);
}class MyClass { int data;
};
int main() {int* p1 = new int(42); // 会调用我们重载的全局 operator newdelete p1;             // 会调用我们重载的全局 operator deleteMyClass* obj = new MyClass; // 同样会调用我们的版本delete obj;return 0;
}

第二种方式:

类特定重载 (非常有用且推荐):

#include <iostream>
#include <stdlib.h>
using namespace std;class MyClass {
public:int _data;// 类特定的 operator newstatic void* operator new(size_t size) {cout << "MyClass::new called, size: " << size <<endl;void* p = malloc(size);if (!p) throw bad_alloc();return p;}// 类特定的 operator deletestatic void operator delete(void* p) noexcept {cout << "MyClass::delete called" <<endl;free(p);}
};int main() {MyClass* obj = new MyClass; // 调用 MyClass::operator newdelete obj;                 // 调用 MyClass::operator deleteint* p = new int; // 仍然使用全局的 ::operator new,不受影响delete p;return 0;
}

我们只为我们特定的类重载 operator new 和 operator delete。这样,只有分配和释放这个类的对象时,才会使用我们自定义的版本,不会影响程序的其他部分。

下面将使用一表来把operator new与operator delete和new和delete之间的区别做个总结:

总结如下:

特性new/deleteoperator new/operator delete
身份操作符函数
职责完成的对象生命周期管理(分配内存加上构造或者析构)仅负责原始内存的分配和释放
可重载性不可重载可以重载
调用关系调用operator new和构造函数被new所调用(已通过观察汇编进行了对应的证明)

7、new 和 delete 表达式的编译期魔法

最后一个部分:对上面的内容做一个更深层次的理解:

当编译器看到 MyClass *obj = new MyClass; 这行代码时,它会进行一个固定的分解动作。这个过程是理解重载底层原理的关键。

// 我们自己写的代码:
MyClass *obj = new MyClass(arg1, arg2);// 编译器在背后实际生成的代码:
void* __memory = nullptr; // 1. 先申请原始内存
try {__memory = MyClass::operator new(sizeof(MyClass)); // 寻找分配函数obj = static_cast<MyClass*>(__memory);obj->MyClass::MyClass(arg1, arg2); // 2. 在内存上构造对象(调用构造函数)
} catch (...) {if (__memory)MyClass::operator delete(__memory); // 如果构造失败,释放申请的内存throw; // 重新抛出异常
}

同理,delete也是一样。

// 我们自己写的代码:
delete obj;// 编译器在背后实际生成的代码:
obj->~MyClass(); // 1. 先调用析构函数
MyClass::operator delete(obj); // 2. 再释放内存

重载 operator new 和 operator delete,本质上就是告诉编译器,在执行上述流程的第一步和最后一步时,不要用标准库提供的默认函数,而是用我自定义的函数。

底层做了什么?

1、编译时编译器看到 new MyClass,知道要去 MyClass 的作用域内寻找 operator new 函数。

2、运行时:

new:调用我们自己重写的Myclass::operator new来获取内存,然后调用构造函数完成初始化。

delete:调用析构函数,然后调用我们自己重写的Myclass::operator delete来释放内存。

3、内存布局:重载函数本质是类的静态成员函数,他们不属于任何一个对象实例,因此没有this指针,通俗地讲,他们只是为对象的诞生(创建)和消亡(销毁)提供场地管理的后勤部门!

截止到这里,本文的所有内容就完成了,在写的时候难免有所不足之处,可在评论区指出,本人会及时进程更新,如对您有所帮助可以点赞加收藏,本作者持续更新c/c++或数据结构或linux有关的内容!

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

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

相关文章

【AI开发】【前后端全栈】[特殊字符] AI 时代的快速开发思维

&#x1f680; AI 时代的快速开发思维 —— 以 Django Vue3 为例的前后端分离快捷开发流程 一、AI 时代的开发新思路 在 AI 的加持下&#xff0c;软件开发不再是“纯体力活”&#xff0c;而是 思维工具自动化 的协作。 过去&#xff1a;需求 → 设计 → 开发 → 测试 → 上…

Day24_【深度学习(3)—PyTorch使用—张量的创建和类型转换】

一、创建张量1.张量基本创建方式torch.tensor 根据指定数据创建张量 &#xff08;最重要&#xff09;torch.Tensor 根据形状创建张量, 其也可用来创建指定数据的张量torch.IntTensor、torch.FloatTensor、torch.DoubleTensor 创建指定类型的张量1.1 torch.tensor# 方式一&…

3-12〔OSCP ◈ 研记〕❘ WEB应用攻击▸利用XSS提权

郑重声明&#xff1a; 本文所有安全知识与技术&#xff0c;仅用于探讨、研究及学习&#xff0c;严禁用于违反国家法律法规的非法活动。对于因不当使用相关内容造成的任何损失或法律责任&#xff0c;本人不承担任何责任。 如需转载&#xff0c;请注明出处且不得用于商业盈利。 …

AI 大模型赋能智慧矿山:从政策到落地的全栈解决方案

矿山行业作为能源与工业原料的核心供给端&#xff0c;长期面临 “安全生产压力大、人工效率低、技术落地难” 等痛点。随着 AI 大模型与工业互联网技术的深度融合&#xff0c;智慧矿山已从 “政策引导” 迈入 “规模化落地” 阶段。本文基于 AI 大模型智慧矿山行业解决方案&…

Node.js 项目依赖包管理

h5打开以查看 一、核心理念&#xff1a;从“能用就行”到“精细化管理” 一个规范的依赖管理体系的目标是&#xff1a; 可复现&#xff1a;在任何机器、任何时间都能安装完全一致的依赖&#xff0c;保证构建结果一致。 清晰可控&#xff1a;明确知道每个依赖为何存在&#x…

洛谷P1835素数密度 详解

题目如下&#xff1a;这里面有部分代码比较有意思&#xff1a;1&#xff0c;为何开始先遍历&#xff0c;最终值小于50000&#xff1f;因为题目要求的右边与左边差小于 10^6 &#xff0c;所以最多有10^3个素数&#xff0c;所以保存里面的素数数量大于1000&#xff0c;而50000的化…

突破限制:FileCodeBox远程文件分享新体验

文章目录【视频教程】1.Docker部署2.简单使用演示3. 安装cpolar内网穿透4. 配置公网地址5. 配置固定公网地址在隐私日益重要的今天&#xff0c;FileCodeBox与cpolar的协同为文件传输提供了安全高效的解决方案。通过消除公网IP限制和隐私顾虑&#xff0c;让每个人都能掌控自己的…

以太网链路聚合实验

一、实验目的掌握使用手动模式配置链路聚合的方法掌握使用静态 LACP 模式配置链路聚合的方法掌握控制静态 LACP 模式下活动链路的方法掌握静态 LACP 的部分特性的配置二、实验环境安装有eNSP模拟器的PC一台&#xff0c;要求PC能联网。三、实验拓扑LSW1与LSW2均为S3700交换机。L…

autMan安装教程

一、安装命令 如果你系统没安装docker&#xff0c;请看往期教程 以下为通用命令 docker run -d --name autman --restart always -p 8080:8080 -p 8081:8081 -v /root/autman:/autMan --log-opt max-size10m --log-opt max-file3 hdbjlizhe/autman:latest解释一下以上命令&…

【无人机】自检arming参数调整选项

检查项目 (英文名)中文含义检查内容四旋翼建议 (新手 → 老手)理由说明All所有检查启用下面所有的检查项目。✅ 强烈建议勾选这是最安全的设置&#xff0c;确保所有关键系统正常。Barometer气压计检查气压计是否健康、数据是否稳定。✅ 必须勾选用于定高模式&#xff0c;数据异…

数字图像处理(1)OpenCV C++ Opencv Python显示图像和视频

Open CV C显示图像#include <iostream> #include <opencv2/opencv.hpp> using namespace cv;//包含cv命名空间 int main() {//imread(path)&#xff1a;从给定路径读取一张图片&#xff0c;储存为Mat变量对象Mat img imread("images/love.jpg");//named…

【芯片设计-信号完整性 SI 学习 1.2.2 -- 时序裕量(Margin)】

文章目录1. 什么是时序裕量&#xff08;Margin&#xff09;1. 背景&#xff1a;为什么需要数字接口时序分析2. 时钟周期方程3. Setup 裕量 (tMARGIN_SETUP)4. Hold 裕量 (tMARGIN_HOLD)5. 设计注意事项6. 实际应用场景2. 时序裕量的来源3. 测试方法(1) 眼图测试 (Eye Diagram)(…

AOP 切面日志详细

在业务方法上打注解package com.lib.service;Service public class BookService {LogExecution(description "查询图书")public Book query(int id) {return repo.findById(id);}LogExecution(description "借阅图书")public void borrow(int id) {// 模…

使用paddlepaddle-Gpu库时的一个小bug!

起初安装的是 paddlepaddle 2.6.1版本。 用的是Taskflow的快速分词以及ner快速识别&#xff1a;​​​​​​​seg_accurate Taskflow("word_segmentation", mode"fast") ner Taskflow("ner", mode"fast")但是使用不了Gpu。想使用Gp…

量子能量泵:一种基于并联电池与电容阵的动态直接升压架构

量子能量泵&#xff1a;一种基于并联电池与电容阵的动态直接升压架构 摘要 本文提出了一种革命性的高效电源解决方案&#xff0c;通过创新性地采用并联电池组与串联高压电容阵相结合的架构&#xff0c;彻底解决了低电压、大功率应用中的升压效率瓶颈与电池一致性难题。该方案摒…

【Linux网络】网络基础概念——带你打开网络的大门

1. 计算机网络背景 文章目录1. 计算机网络背景网络发展2. 初识协议2.1 协议分层软件分层的好处2.2 OSI七层模型2.3 TCP/IP五层(或四层)模型网络发展 独立模式 独立模式是计算机网络发展的最初阶段&#xff0c;主要特点如下&#xff1a; 单机工作环境&#xff1a; 每台计算机完…

简单介绍一下Clickhouse及其引擎

一、ClickHouse 的优缺点一、ClickHouse 的优点 ✅ 1. 极致的查询性能 列式存储&#xff1a;只读取查询涉及的列&#xff0c;大幅减少 IO。数据压缩&#xff1a;常见压缩率 5~10 倍&#xff0c;减少存储和带宽消耗。向量化执行&#xff1a;按批次&#xff08;block&#xff09;…

【卷积神经网络详解与实例】8——经典CNN之VGG

1 开发背景 VGGNet是牛津大学视觉几何组(Visual Geometry Group)提出的模型&#xff0c;该模型在2014ImageNet图像分类与定位挑战赛 ILSVRC-2014中取得在分类任务第二&#xff0c;定位任务第一的优异成绩。其核心贡献在于系统性地探索了网络深度对性能的影响&#xff0c;并证明…

【分享】中小学教材课本 PDF 资源获取指南

很多人都不知道&#xff0c;其实官方提供的中小学教材课本 PDF 文档是完全免费且正版的&#xff0c;无需使用扫描版&#xff0c;清晰度和质量都非常高。 这些资源就藏在国家中小学智慧教育平台&#xff08;basic.smartedu.cn&#xff09;上。这个平台涵盖了从小学到高中的各个…

js趣味游戏 贪吃蛇

以下是关于JavaScript趣味游戏的系统性整理&#xff0c;涵盖经典案例、开发工具、教程资源及创意方向&#xff0c;助您快速掌握JS游戏开发的核心逻辑&#xff1a;一、经典JS趣味游戏案例贪吃蛇&#xff08;Snake Game&#xff09;核心机制&#xff1a;键盘控制蛇的移动方向&…