首页 > 系统相关 >内存管理new and delete(C++)

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

时间:2024-04-10 18:30:29浏览次数:21  
标签:调用 函数 C++ 空间 operator new delete

        在本篇中,将会较为详细的介绍在 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 申请空间时会调用构造函数,清理空间时会调用析构函数。 

标签:调用,函数,C++,空间,operator,new,delete
From: https://blog.csdn.net/m0_74830524/article/details/137175159

相关文章

  • CF1913B Swap and Delete 题解
    翻译给定一个字符串\(s\),你有两种操作:删除一个字符。(花费一枚金币)交换某两个字符的位置。(不花费金币)假设经过若干次操作后得到的字符串为\(t\)。\(t\)是好的当且仅当对于任意的\(i\)(\(1\lei\le|t|\),\(|t|\)为字符串\(t\)的长度),均满足\(t_i\nes_i\)。(\(s\)是......
  • UE中创建Actor添加组件初始化(UEC++个人学习笔记)
    在ue中创建actorc++类,在actor的.h文件中添加五个组件又由上到下的作用分别为:获取下SceneComponent,用于操作其Transform等相应接口。获取静态模型组件。获取盒子碰撞组件。获取粒子特效组件。获取音频组件。#include"Components/SceneComponent.h"#include"Components......
  • C++核心编程
    C++核心编程本阶段主要针对C++面向对象编程技术做详细讲解,探讨C++中的核心和精髓。1内存分区模型C++程序在执行时,将内存大方向划分为4个区域代码区:存放函数体的二进制代码,由操作系统进行管理的全局区:存放全局变量和静态变量以及常量栈区:由编译器自动分配释放,存放......
  • new mars3d.graphic.PolylineEntity({实现航线真实穿过山体或者模型的部分用虚线展示
    1.在官网示例中通过 newmars3d.graphic.PolylineEntity({实现航线真实穿过山体或者模型的部分用虚线展示效果2.示例地址:功能示例(Vue版)|Mars3D三维可视化平台|火星科技3.实现效果: 1.航线真实穿过山体或者模型的部分用虚线展示、并且是(真实穿过不是视线挡住那种),遮挡......
  • ROS中自定义全局算法规划器(c++)
     ros中编写一个全局路径规划器并集成为ros插件,加载到turtlebot3机器人平台上仿真验证参考资料:ROS中自定义全局规划器(上)_算法部署_哔哩哔哩_bilibili官网教程:navigation/Tutorials/WritingAGlobalPathPlannerAsPlugininROS-ROSWiki1.建立工作空间mkdir-pjps_......
  • C++_STL提供了六大组件
    STL提供了六大组件StandardTemplateLibrary容器:Containers各种数据结构,如vector,list,deque,set,mep等。容器是类模板。在声明容器变量时,可以指定容器将保存的元素的类型算法:各种常用的算法,提供了执行各种操作的方式,包括对容器内容执行初始化,排序,搜索和转换等操作,比如sort,s......
  • C++笔试面试题整理
    常见C++笔试面试题整理1.C和C++的区别C是面向过程的语言,是一个结构化的语言,考虑如何通过一个过程对输入进行处理得到输出;C++是面向对象的语言,主要特征是“封装、继承和多态”。封装隐藏了实现细节,使得代码模块化;派生类可以继承父类的数据和方法,扩展了已经存在的模块,实现......
  • C++的保护类型还能这么玩
    将一个类的析构函数定义为 protected保护类型:这个类就不能在外部被析构,被定义。只能在它的子类,或者它的友元类里面去定义。定义了保护类型的析构函数,它的声明周期在子类或者友元类里面自动管理。最主要理解它的限制,理解生命周期就好。 定义为保护类型的好处:将一个类......
  • C++初阶:6.string类
    string类string不属于STL,早于STL出现看文档C++非官网(建议用这个)C++官网文章目录string类一.为什么学习string类?1.C语言中的字符串2.两个面试题(暂不做讲解)二.标准库中的string类1.string类(了解)2.string类的常用接口说明(注意下面我只讲解最常用的接口)(1)......
  • C++类拷贝控制 深拷贝 浅拷贝
    参考博文:https://www.cnblogs.com/zhxmdefj/p/11579364拷贝构造函数,拷贝赋值运算符拷贝构造函数第一个参数是自身类类型引用,其他参数都有默认值的构造函数就是拷贝构造函数。classSales_data{public:   Sales_data();  //默认构造函数   Sales_data(const......