C++(9.5)——浅谈new和delete的实现原理

(注:本文是针对上篇文章中C++内存管理的两个关键字)两个关键字原理的解析,对于这两个关键字的使用并没有什么影响,如果只想得知两个关键字的使用方法,则可以直接跳过本篇文章)

目录

[1. 引入:](#1. 引入:)

[2.operator new 与 operator delete:](#2.operator new 与 operator delete:)

[2.1 基本定义以及与操作符的差异:](#2.1 基本定义以及与操作符的差异:)

[2.2 为什么要引入operator new和operator delete:](#2.2 为什么要引入operator new和operator delete:)

[3. 操作符的大致动作过程:](#3. 操作符的大致动作过程:)

[3.1 开辟单个空间的动作过程:](#3.1 开辟单个空间的动作过程:)

[3.2 开辟多个空间的动作过程:](#3.2 开辟多个空间的动作过程:)


1. 引入:

为了方便说明两个关键字的实现原理,首先引入一个简单的栈,具体代码如下:

cpp 复制代码
#include<iostream>
using namespace std;

class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack( int capacity = 4)" << endl;
		_a = new int[capacity];
		_capacity = capacity;
		_top = _capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[]_a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack s1;
	return 0;
}

运行代码,结果显示调用了一次构造函数和一次析构函数:

对于下方给出的代码,即:

cpp 复制代码
Stack* s2 = new Stack;
	delete s2;

整体的运行顺序为:利用关键字开辟一个类型为自定义类型的空间,大小为字节。此后,由自定义类型的构造函数可知,再利用关键字为指针变量**_** 开辟空间。因此,第一行代码整体开辟了两次空间。第一次是自身开辟空间,第二次是针对自定义类型会去调用自定义类型的构造函数,在构造函数中,再开辟一次空间。

对于第二行代码中的关键字。首先需要调用析构函数,析构函数的作用并非像一样释放掉开辟的空间,而是释放掉空间中的资源,也就是指针变量**** 指向的空间。在调用完析构函数后,再去释放空间。此处可以看出来,针对自定义类型,在释放空间时,并不能区调用。因为并不会处理指针变量****中已经开辟的空间。因此会导致内存泄漏。

由上面的例子和上篇文章引入关键字使用方法的例子可以了解,针对内置类型与并没有差异,针对自定义类型,多了一步调用默认构造函数。对于,针对内置类型与也没有差异,针对自定义类型,多了一步在释放空间之前调用一次析构函数。所以,这两个关键字可以看作对的加强。对于这两个关键字开辟空间或者释放空间的功能的原理,是借助,完成的。需要注意,上面给出的是两个全局函数,并非运算符重载。下面将针对这两个全局函数进行解析。

2.operator new 与 operator delete:

2.1 基本定义以及与操作符的差异:

和**** 并不是运算符重载,而是两个全局函数,对于,其运用方式与基本相同,的调用方式也基本相同。二者与前面的操作符在运行中也有一定的差距,例如:

cpp 复制代码
Stack* s2 = new Stack;
	delete s2;

	Stack* s3 = (Stack*)operator new(sizeof(Stack));
	operator delete(s3);

运行后结果如下:

不难发现,两个全局函数只能开辟空间,并不能像操作符一样调用构造函数或者析构函数。

对于这两个全局函数,具体代码如下:
(注:对于下方给出的代码在此阶段并不需要知道具体含义,在文章的后面,需要引用其中某行代码时,会给出相应的解析)

cpp 复制代码
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
void *p;
while ((p = malloc(size)) == 0)
     if (_callnewh(size) == 0)
     {
         static const std::bad_alloc nomem;
         _RAISE(nomem);
     }
return (p);
}
cpp 复制代码
void operator delete(void *pUserData)
{
     _CrtMemBlockHeader * pHead;
     RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
     if (pUserData == NULL)
         return;
     _mlock(_HEAP_LOCK);  
     __TRY
         pHead = pHdr(pUserData);
         _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
         _free_dbg( pUserData, pHead->nBlockUse );
     __FINALLY
         _munlock(_HEAP_LOCK); 
     __END_TRY_FINALLY
     return;
}


#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)

通过上面给定代码中的其中两行,即:

cpp 复制代码
while ((p = malloc(size)) == 0)
_free_dbg( pUserData, pHead->nBlockUse );

不难看出,这两个函数可以看作是对这两个函数的封装。而对于为什么要对进行一次封装再使用,而不直接使用,将在下一小节进行简要说明。

2.2 为什么要引入operator new和operator delete:

若调用开辟空间失败,则一般会返回。但是,在中,面向对象的编程并不能在失败用返回值进行处理,而是需要抛异常,对进行封装,正是为了解决这个问题

(注:对于抛异常等相关内容将会在后续的文章中给出,在此阶段只需要这个概念即可)

在上面给出的代码中,虽然具体内容并不能了解清楚,但是对于下面的代码,即:

cpp 复制代码
void *p;
while ((p = malloc(size)) == 0)
     if (_callnewh(size) == 0)
     {
         // report no memory
         // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
         static const std::bad_alloc nomem;
         _RAISE(nomem);
     }

在介绍时就提到,当成功的开辟空间后,返回值会返回这块空间的起始地址。因此,上述代码的大体意思为:检查的返回值,如果返回值判断等于,则说明没有成功的开辟 地址,下面就进行抛异常。对于的封装大致意思也相同,此处不再过多介绍。

3. 操作符的大致动作过程:

3.1 开辟单个空间的动作过程:

前面简单介绍了两个全局函数。本部分将介绍操作符的大致动作过程。

(注:为了清楚的了解的动作过程,需要通过汇编进行查看,本部分并不需要了解汇编代码,只是借用其中的几行来大体说明动作过程,并且针对借用的代码给出解析)

对于下面给出的代码:

cpp 复制代码
Stack* s2 = new Stack;

转为汇编形式,即为:

在上面的指令中,可以找到较为熟悉的两行指令,即:

二者分别对应了操作符的两个动作,即:调用函数开辟空间,调用构造函数对空间进行初始化。 对于操作符同理,其汇编指令如下:

其中,红色框框出来的两行分别为:调用析构函数与调用释放空间。

3.2 开辟多个空间的动作过程:

给定代码如下:

cpp 复制代码
Stack* s4 = new Stack[10];
	delete[]s4;

将上述代码转为汇编形式,涉及到的指令如下:

可以看到,大致的运行过程是先调用指令,下一步直接会转到。其中的表示开辟空间的大小。

相关推荐
nenchoumi31194 分钟前
UE5 学习系列(九)光照系统介绍
java·学习·ue5
南無忘码至尊6 分钟前
Unity C# 入门基础知识点整理与实战技巧
开发语言·c#
江梦寻7 分钟前
软件工程教学评价
开发语言·后端·macos·架构·github·软件工程
iCxhust8 分钟前
汇编字符串比较函数
c语言·开发语言·汇编·单片机·嵌入式硬件
张乔2415 分钟前
spring boot项目整合mybatis实现多数据源的配置
java·spring boot·多数据源
GzlAndy19 分钟前
Tomcat调优
java·tomcat
美好的事情能不能发生在我身上21 分钟前
苍穹外卖Day11代码解析以及深入思考
java·spring boot·后端·spring·架构
辉辉健身中28 分钟前
Maven入门(够用)
java·maven
星火飞码iFlyCode42 分钟前
【无标题】
java·前端·人工智能·算法
不良手残1 小时前
Redisson + Lettuce 在 Spring Boot 中的最佳实践方案
java·spring boot·redis·后端