内存管理new and delete(C++)

        在本篇中,将会较为详细的介绍在 Cpp 中的两个新操作符 new 和 delete,将会介绍其中的底层原理,以及这两个操作符的使用方法。其中还介绍了 new/delete 操作符使用的细节,还扩展了一些有关定位 new 表达式的知识点。最后总结了 malloc/free 与 new/delete 的区别。

目录

1. C++中的内存管理方式

1.1 new/delete内置类型

1.2 new/delete自定义类型

1.3 new/delete的其他操作

2. new/delete实现原理

2.1 operator new 与 operator delete 函数

2.2 operator new[] 与 operator delete[] 函数

2.3 new/delete的使用细节

3. 定位new表达式

4. 总结malloc/free与new/delete的区别

1. C++中的内存管理方式

        在Cpp中的内存管理方式其实和C语言中的内存管理方式相差无几,在C语言中能使用的内存管理方式在Cpp中同样适用,不过在Cpp中适用两个新操作符 new delete 将 malloc 和 free 这两个函数给取代了。

1.1 new/delete内置类型

        以下为 new/delete 对于内置类型的操作,如下:

        以上就是对 new 操作的使用,对于 ptr1 来说,我们只是分配了一个 int 类型的空间,对于 ptr2 来说,我们使用 new 分配了一个 int 类型的空间并且将其初始化为 10,对于 ptr3 来说,使用类似数组的形式,给其分配了10个 int 类型的空间,我们还可以像初始化数组一样,给 ptr3 初始化,对于剩下未初始化的数组元素,默认使用0进行初始化。接着使用 delete 函数将其的空间释放。

        注:申请和释放单个元素的空间,使用 new 和 delete操作符,申请和释放连续的空间使用 new[] 和 delete[],需要注意的是,我们要将其匹配使用

1.2 new/delete自定义类型

        以下为 new delete 对于自定义类型变量的操作,如下:

        如上所示,当我们使用 new 和 delete 操作符时的时候,对于自定义类型,new 和 delete 会默认调用其构造函数和析构函数。而相对比而言,malloc 和 free 就只是简单的申请一份空间和释放一份空间。

1.3 new/delete的其他操作

        在C语言创建一个链表的时候,我们经常使用 malloc 来创建链表,现在我们可以使用 new 来创建链表,如下:

struct LinkNode {LinkNode* _next;int _data;// 构造函数LinkNode(int x= 0):_next(nullptr),_data(x){}
};// 创建 num 个结点的链表
LinkNode* CreateLinkList(int num) {LinkNode head(-1);LinkNode* tail = &head;int val = 0;printf("Please input the val in order:");for (int i = 0; i < num; i++) {cin >> val;LinkNode* newnode = new LinkNode(val);tail->_next = newnode;tail = tail->_next;}return head._next;
}

        该操作相对 malloc 函数来说,方便多了,我们在使用 new 开辟空间的时候,也不需要判断是否申请失败,因为使用 new 开辟的空间,会有编译器自动检测是否申请失败。这样的代码写起来也会更方便。

        通过以上的代码编写,其实我们也可以总结出一些关于使用 new delete 的优点:

        1. 在创建对象的时候可以对对象进行初始化

        2. 不需要检查是否会存在申请失败的情况

        3. new 写起来更轻便,不需要计算大小,直接就可以进行申请变量或数组需要的空间

        4. new 和 delete 对于自定义类型的变量会调用构造函数和析构函数。

2. new/delete实现原理

        接下来我们将探讨 new/delete 的实现原理,不光探讨这两个,我们还会探讨 new[]/delete[] 函数的实现原理。

2.1 operator new 与 operator delete 函数

        在Cpp中其实存在两个底层函数(也就是很少使用的函数,其他操作符的底层实现函数)。如下所示:

        当我们对我们的程序进行 Debug 的时候,转入反汇编,发现对于操作符 new delete,在底层调用的是 operator new() 和 operator delete() 函数。

        对于 operator new() 和 operator delete() 函数的底层实现如下:

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid* p;while ((p = malloc(size)) == 0)if (_callnewh(size) == 0){// report no memory// 如果申请内存失败了,这里会抛出bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);
}#define free(p) _free_dbg(p, _NORMAL_BLOCK)void operator delete(void* pUserData)
{_CrtMemBlockHeader* pHead;RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));if (pUserData == NULL)return;_mlock(_HEAP_LOCK); /* block other threads */__TRY/* get a pointer to memory block header */pHead = pHdr(pUserData);/* verify block type */_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));_free_dbg(pUserData, pHead->nBlockUse);  // 调用free函数__FINALLY_munlock(_HEAP_LOCK); /* release other threads */__END_TRY_FINALLYreturn;
}

        如上所示的代码,对于这两个函数而言,其实底层调用的函数也是 malloc 以及 free 函数,只不过还会多加入一些其他的东西,比如在 operator new 函数中,若调用 new 失败了,那么就会自动的抛异常,函数中的这些举动让我们使用 new 起来也更加轻松。

        所以,我们对于 new 和 delete 的实现,可以总结为下图:

        对于 new 来说,我们是先调用 operator new 函数开辟空间,然后调用构造函数,对生成的变量进行构造。而对于析构函数而言,我们要先进行析构,然后在进行调用operator delete 函数去释放空间。因为对于 delete 而言,如果先进行调用 operator delete函数,那么就提前将空间释放了,若我们的析构函数中还需要对变量进行释放空间,那么这个时候就找不到该释放的空间了。

2.2 operator new[] 与 operator delete[] 函数

        以上探讨完 new/delete 的原理后,现在我们也要开始探讨 new[]/delete[] 的实现原理了。既然 new/delete 和 operator new 与 operator delete 函数有关,那么 new[]/delete[] 也许会和 operator new[] 与 operator delete[] 函数 有关,以下将探讨这两个函数。 如下:

        如上图所示,我们使用 new[] 时,调用的函数为 operator new[] 函数,那么对于这样的一个函数,其中封装的也是 operator new 函数,如下:

        那么接下来我们来观察 delete[] ,对于上图:

        我们发现在 push 时,push 进的值为 2Ch,十进制也就是 44,但是对于我们的类 A 来说,一个 A 类对象也就4个字节,十个也应该是40,为什么会是44呢,这是因为 delete 的独特机制,如下:

        上图中,红框表示 p1 所在内存地址,篮筐则是系统对 p1 多加入的一个值,其值为 a(10),刚好对应了 p1 中元素的个数,这是因为系统在调用 delete[] 操作符的时候,并不知道的该调用多少次析构函数,所以在 p1 的前一个地址处开辟了一个 int 型的空间,用于存储 p1 的个数,便于在调用 delete[] 时,知道应该调用多少次析构函数。

        其实对于 delete[] 函数而言,和 new[] 几乎是同样类型的底层逻辑。所以对于 new[] 和 delete[] 而言,其调用的顺序为:

2.3 new/delete的使用细节

        以上已经介绍了许多细节,接下来将会进行介绍其中的使用细节:使用 new 要和 delete 配合使用,使用 new[] 要和 delete[] 配合使用,如下:

        如上所示,使用两次同样的方式调用函数,用 new[] 和 delete 搭配使用,一个报警告,一个没有报警告。这是因为对于内置类型来说,不会调用析构函数,那么在使用 delete[] 和 delete 其实是差不多的,因为内置类型不会调用析构函数。但是对于内置类型来说,我们在以上已经说过,当使用自定义类型的时候,系统会多申请一块 int 类型的空间,但是通常系统对于数组的使用,使用的是 int 类型之后的空间,若直接使用 delete 函数进行清除空间操作,那么就相当于在一整块空间的中间部分处开始释放空间,这样肯定会导致报错。

       

        但是当我们将类的析构函数给注释掉的时候,又会是怎么样呢?如下图所示:

        此时得到的结果表示为正常运行退出,并没有报错。通过调试我们发现,原本要在空间看开辟的一块 int 大小的空间也没有了,这是为什么呢?

        这是因为编译器对代码进行了优化(不同的编译器得出的结果可能不同,所以这不是固定答案),我们将析构函数给注释掉了,而编译器默认生成的析构函数也不会做什么,所以对于编译器来说,直接将这一步骤给省略了,因为就算知道要进行多少次析构,实际上也并没有什么用,所以就将原本准备开辟的空间给取消了。这个时候也不会从中间位置开始释放空间,所以就不会报错了。

3. 定位new表达式

        对于定位 new 表达式来说,我们先对其的用法进行说明,使用格式如下:

int main() {A* p1 = (A*)operator new(sizeof(A));// 显示调用构造函数对一块已经有的空间进行初始化new(p1)A(10);p1->~A();operator delete(p1);return 0;
}

        对以上格式总结来说,就是像调用 malloc 函数一样调用 operator new,然后使用 new(place_address)type(initial),其中 new 旁边的括号中为指针,type 表示类,initial 表示初始化的值。对于删除而言,就算先调用对象的析构函数,然后调用 operator delete 函数将其删去空间。但是这样做的意义是什么呢?为什么不直接调用 new 和 delete 呢?

        因为:定位 new 表达式在实际中一般配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。我们平时使用的 new 不是将在内存池中申请空间,而是直接在内存中申请空间。
        所以,定位 new 表达式相当于 new 的一种特殊使用场景。

4. 总结malloc/free与new/delete的区别

        对于 malloc/free 和 new/delete 来说,他们之间的相同点为都是在堆上开辟空间,都需要由我们手动释放。

        他们之间的不同点为:

        1. malloc/free 是函数,而 new/delete 是操作符

        2. malloc 申请的空间不可以进行初始化,而 new 申请的空间可以进行初始化

        3. malloc 申请空间时,需要手动计算申请的空间大小,而 new 只需要在其后跟上空间类型即可,若是多个对象,只需要在 [ ] 中指定个数

        4. malloc 申请空间是返回 NULL,需要我们自己判断是否申请成功,而 new 申请空间失败时会抛异常,不需要我们自己判断是否申请成功

        5. malloc/free 函数只是进行简单的申请空间和清理空间,而 new/delete 申请空间时会调用构造函数,清理空间时会调用析构函数。 

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

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

相关文章

【简单讲解下C++max函数的使用】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

动态支付策略:Go 语言中策略模式的妙用

关注公众号【爱发白日梦的后端】分享技术干货、读书笔记、开源项目、实战经验、高效开发工具等&#xff0c;您的关注将是我的更新动力&#xff01; 在现代软件架构中&#xff0c;支付功能是不可或缺的一环。无论是在线购物还是虚拟服务&#xff0c;支付策略的选择直接影响用户体…

用API技术为数据安全“上保险”——双重保障

&#x1f50d;API在数据安全领域的核心地位 随着数字化进程的狂飙突进&#xff0c;应用程序接口&#xff08;API&#xff09;已化身为企业内部、不同平台间以及用户交互的关键纽带。它们不仅是数据流动与共享的驱动引擎&#xff0c;更是守护数据安全的重要防线。其中&#xf…

Longan Pi 3H 开发板体验

Longan Pi 3H 开发板体验 开箱内容 打开包装&#xff0c;你可以看到以下物品 一个Longan Pi 3H盒子Longan Pi 3H开发板 产品基本介绍 Longan Pi 3H 是基于 Longan Module 3H 核心板的 ARM Linux 开发板&#xff0c;以 H618 (Quad core ARM Cortex-A531.5Ghz , 64-bit) 为主控…

FPN网络

FPN&#xff08;Feature Pyramid Network&#xff09;是一种用于目标检测和语义分割等计算机视觉任务的网络结构。它旨在解决不同尺度下的特征信息不足的问题&#xff0c;提高模型对小目标和远距离目标的检测能力。在目标检测任务中&#xff0c;由于目标的尺度和形状各异&#…

人民大学:揭示大语言模型事实召回的关键机制

引言&#xff1a;大语言模型事实召回机制探索 该论文深入研究了基于Transformer的语言模型在零射击和少射击场景下的事实记忆任务机制。模型通过任务特定的注意力头部从语境中提取主题实体&#xff0c;并通过多层感知机回忆所需答案。作者提出了一种新的分析方法&#xff0c;可…

CICD流水线 发布应用到docker镜像仓库

准备工作 1.先注册免费的镜像仓库 复制链接: https://cr.console.aliyun.com/cn-beijing/instances 实施 1. 新建流水线&#xff0c;选择模板 2.添加流水线源&#xff0c;及是你的代码仓库, 选择对应分支. 3.代码检查以及单元测试&#xff0c;这个步骤可以不用动它. 4. …

【随笔】Git 基础篇 -- 分支与合并 git merge(九)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

7款公司电脑监控软件

7款公司电脑监控软件 研究证明&#xff0c;人们在家办公的效率比在办公室办公的效率低一半&#xff0c;其中原因是缺少监督&#xff0c;即便在公司办公&#xff0c;还存在员工偷闲的时刻&#xff0c;比如聊天、浏览无关网站、看剧、炒股等&#xff0c;企业想提高员工的工作效率…

xilinx AXI CAN驱动开发

CAN收发方案有很多&#xff0c;常见的解决方案通过是采用CAN收发芯片&#xff0c;例如最常用的SJA1000,xilinx直接将CAN协议栈用纯逻辑实现&#xff0c;AXI CAN是其中一种&#xff1b; 通过这种方式硬件上只需外接一个PHY芯片即可 上图加了一个电平转换芯片 软件设计方面&…

vivado 配置存储器器件编程2

为双 QSPI (x8) 器件创建配置存储器文件 您可使用 write_cfgmem Tcl 命令来为双 QSPI (x8) 器件生成 .mcs 镜像。此命令会将配置数据自动拆分为 2 个独立 的 .mcs 文件。 注释 &#xff1a; 为 SPIx8 生成 .mcs 时指定的大小即为这 2 个四通道闪存器件的总大小。…

电脑远程控制esp32上的LED

1、思路整理 首先esp32需要连接上wifi 然后创建udp socket 接受udp数据 最后解析数据&#xff0c;控制LED 2、micropython代码实现 import network from socket import * from machine import Pin p2Pin(2,Pin.OUT)def do_connect(): #连接wifi wlan network.WLAN(network.…