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

news2024/11/27 13:49:26

        在本篇中,将会较为详细的介绍在 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 bytes
	void* 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_FINALLY
		return;
}

        如上所示的代码,对于这两个函数而言,其实底层调用的函数也是 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.coloradmin.cn/o/1578123.html

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

相关文章

C++练级之路——类和对象(上)

1、类的定义 class 类名{//成员函数 //成员变量}; class为定义的关键字&#xff0c;{ }内是类的主体&#xff0c;注意后面的 ; 不要忘了 类体中的内容成为类的成员&#xff0c;类中的变量为成员变量或类的属性&#xff0c;类中的函数为成员函数或类的方法&#xff0c; 类的两种…

Prompt最佳实践|大模型也喜欢角色扮演?

在OpenAI的官方文档中已经提供了Prompt Enginerring的最佳实践&#xff0c;目的就是帮助用户更好的使用ChatGPT 编写优秀的提示词我一共总结了9个分类&#xff0c;本文讲解第2个分类&#xff1a;要求模型扮演角色 提供更多的细节要求模型扮演角色使用分隔符指定任务步骤提供样…

OPC UA遇见chatGPT

最近opc 基金会将召开一个会议&#xff0c;主题是”OPC UA meets IT“。由此可见&#xff0c;工业自动化行业也开始研究和评估chatGPT带来的影响了。 本文谈谈本人对OPC UA 与chatGPT结合的初步实验和思考。 构建OPC UA 信息模型 chatGPT 的确非常强大了&#xff0c;使用自然…

前端开发之el-table(vue2中)固定列fixed滚动条被固定列盖住

固定列fixed滚动条被固定列盖住 效果图前言解决方案 效果图 前言 在使用fixed固定列的时候会出现滚动条被盖住的情况 解决方案 改变el-table固定列的计算高度即可 .el-table {.el-table__fixed-right,.el-table__fixed {height:auto !important;bottom:15px !important;}}

安装cuda后只在root用户下可见,非root不可见问题

0. 安装cuda和nvidia driver步骤可以参考这篇&#xff1a; https://blog.csdn.net/mygugu/article/details/137474101?spm1001.2014.3001.5502 1.问题记录&#xff1a; 这里记录下安装cuda后遇到的一个奇葩问题&#xff0c;因为安装过程需要root权限&#xff0c;安装后发现…

k8s部署efk

环境简介&#xff1a; kubernetes: v1.22.2 helm&#xff1a; v3.12.0 elasticsearch&#xff1a; 8.8.0 chart包&#xff1a;19.10.0 fluentd: 1.16.2 chart包&#xff1a; 5.9.4 kibana: 8.2.2 chart包&#xff1a;10.1.9 整体架构图&#xff1a; 一、Elasticsearch安装…

Git入门实战教程之创建版本库

一、Git简介 Git是一个分布式版本控制系&#xff0c;分层结构如下&#xff1a; Git分为四层&#xff1a; 1、工作目录 当前正在工作的项目的实际文件目录&#xff0c;我们执行命令git init时所在的地方&#xff0c;也就是我们执行一切文件操作的地方。 2、暂存区 暂存区是…

字符串2s总结

4.字符串 字符串理论基础 什么是字符串 字符串是若⼲字符组成的有限序列&#xff0c;也可以理解为是⼀个字符数组&#xff0c;但是很多语⾔对字符串做了特殊的规定&#xff0c;接下来我来说⼀说C/C中的字符串。 在C语⾔中&#xff0c;把⼀个字符串存⼊⼀个数组时&#xff0c…

前端开发学习笔记 3 (Chrome浏览器调试工具、Emmet语法、CSS复合选择器、CSS元素选择模式、CSS背景)

文章目录 Chrome浏览器调试工具Emmet语法CSS复合选择器后代选择器子选择器并集选择器伪类选择器 CSS元素选择模式元素选择模式概述CSS块标签CSS行内标签CSS行内块标签CSS元素显示模式转换 CSS背景CSS背景颜色CSS背景图片CSS背景图片平铺CSS背景图片位置CSS背景图片固定CSS背景复…

如何高效学习Python编程语言

理解Python的应用场景 不同的编程语言有不同的发展历史和应用场景,了解Python主要应用在哪些领域对于学习它会有很大帮助。Python最初是一种通用脚本语言,主要用于系统级任务自动化。随着时间的推移,它逐步成为数据处理、科学计算、Web开发、自动化运维等众多领域的主要编程语…

第4章 Redis,一站式高性能存储方案,笔记问题

点赞具体要实现功能有哪些&#xff1f; 可以点赞的地方&#xff1a;对帖子点赞&#xff0c;对评论点赞点一次是点赞&#xff0c;再点一次是取消赞统计点赞的数量&#xff08;计数&#xff0c;string&#xff09;&#xff0c;帖子被点赞的数量&#xff0c;某个用户被点赞的数量…

8.java openCV4.x 入门-Mat之多维元组(Tuple)

专栏简介 &#x1f492;个人主页 &#x1f4f0;专栏目录 点击上方查看更多内容 &#x1f4d6;心灵鸡汤&#x1f4d6;我们唯一拥有的就是今天&#xff0c;唯一能把握的也是今天建议把本文当作笔记来看&#xff0c;据说专栏目录里面有相应视频&#x1f92b; &#x1f9ed;文…

【深度学习基础】

打基础日常记录 CNN基础知识1. 感知机2. DNN 深度神经网络&#xff08;全连接神经网络&#xff09;DNN 与感知机的区别DNN特点&#xff0c;全连接神经网络DNN前向传播和反向传播 3. CNN结构【提取特征分类】4. CNN应用于文本 RNN基础1. RNN的本质 词向量模型word2Vec1. 自然语言…

CentOS系统的小小基础

CentOS系统的小小基础 1、基础命令查看系统查看显存 2、常见问题创建文件后出现 E325: ATTENTION Found a swap file by the name ".文件名.swp"自己创建了方便的脚本共所有用户使用yum换源清楚僵尸进程 Linux 使用Anacondapip换源下载安装 NVIDIA Driver 1、基础命令…

语音特征的反应——语谱图

语谱图的横坐标为时间&#xff0c;纵坐标为对应时间点的频率。坐标中的每个点用不同颜色表示&#xff0c;颜色越亮表示频率越大&#xff0c;颜色越淡表示频率越小。可以说语谱图是一个在二维平面展示三维信息的图,既能够表示频率信息,又能够表示时间信息。 创建和绘制语谱图的…

加州大学欧文分校英语基础语法专项课程02:Questions, Present Progressive and Future Tenses 学习笔记

Questions, Present Progressive and Future Tenses Course Certificate 本文是学习 Questions, Present Progressive and Future Tenses 这门课的学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 Questions, Present Progressive and Future TensesWeek 01: …

边缘智能网关为企业数字化转型提供强有力支持-天拓四方

一、企业背景 随着信息技术的飞速发展&#xff0c;企业对于数据处理和通信的需求日益增长。特别是在工业4.0、智能制造等领域&#xff0c;企业面临着海量的数据采集、实时分析、远程监控等挑战。传统的中心化数据处理模式已难以满足这些需求&#xff0c;企业需要寻求一种更加高…

JavaScript - 你知道Ajax的原理吗?如何封装一个Ajax

难度级别:中高级及以上 提问概率:75% 想要实现Ajax,就需要创建它的核心通信对象XMLHttpRequest,通过核心对象的open方法与服务端建立连接,核心对象的send方法可以将请求所需数据发送给服务端,服务端接收到请求并做出响应,我们通过核心对象…

JavaScript(三)-Web APIS

文章目录 DOM事件进阶事件流事件流与两个阶段说明事件捕获事件冒泡阻止冒泡解绑事件 事件委托其他事件页面加载事件元素滚动事件页面尺寸事件 元素尺寸与位置 DOM事件进阶 事件流 什么是事件流 事件流指的是事件完整执行过程中的流动路径 事件流与两个阶段说明 捕获与冒泡 …

windows server 2019-搭建文件共享服务器

一、共享服务器概述 通过网络提供文件共享服务、提供文件下载和上传服务&#xff08;类似FTP服务器&#xff09; 文件共享使用的是CIFS协议&#xff08;微软开发&#xff0c;微软全系服务器都自带此服务&#xff09; FTP服务器对外&#xff08;给客户&#xff09; 文件共享…