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

news2024/9/30 11:39:35

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

目录

1. 引入:

2.operator new 与 operator delete:

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

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

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

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

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


1. 引入:

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

#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;
}

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

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

Stack* s2 = new Stack;
	delete s2;

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

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

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

2.operator new 与 operator delete:

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

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

Stack* s2 = new Stack;
	delete s2;

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

运行后结果如下:

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

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

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);
}
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)

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

while ((p = malloc(size)) == 0)
_free_dbg( pUserData, pHead->nBlockUse );

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

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

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

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

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

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

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

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

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

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

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

   对于下面给出的代码:

Stack* s2 = new Stack;

转为汇编形式,即为:

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

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

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

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

给定代码如下:

Stack* s4 = new Stack[10];
	delete[]s4;

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

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

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

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

相关文章

【Linux笔记】进程等待与程序替换

一、进程的终止 1、进程退出码 在讲解进程的终止之前&#xff0c;先要普及一下进程的退出码概念。 我们父进程之所以要创建子进程&#xff0c;就是为了让子进程运行不一样的任务&#xff0c;那么对于子进程执行的这个任务执行完毕后的结果是否正确或者是否出差错&#xff0c…

学习笔记-数据库概念介绍

一.数据库概述 1.数据库: 存储数据的仓库,本质是一个文件系统. 用户可以对数据库中的数据进行 增加,修改,删除以及查询操作 2.特点 可以结构化存储大量的数据可以有效的保持数据的一致性,完整性读写效率高 3.常用数据库 二.数据库分类及其常用 关系型数据库 指的是二维表格…

渗透测试:保障网络安全的重要手段!

随着信息技术的快速发展&#xff0c;网络安全问题日益受到关注。渗透测试作为一种重要的网络安全检测和评估方法&#xff0c;已经成为保障网络安全的重要手段之一。本文将介绍渗透测试的基本概念、流程、以及在保障网络安全方面的应用。 一、渗透测试的基本概念 渗透测试是一…

linux 服务器上安装 ftp(亲测有效)

目录 1 需求2 安装 1 需求 服务器上需要安装ftp 2 安装 1 下载地址 https://developer.aliyun.com/packageSearch?wordvsftpd或者 https://rpmfind.net/linux/rpm2html/search.php?queryvsftpd2 如何判断 服务器是否安装了ftp rpm -qa | grep vsftpd出现提示则表示已安装…

【从0上手cornerstone3D】如何渲染一个基础的Dicom文件(含演示)

一、Cornerstone3D 是什么&#xff1f; Cornerstone3D官网&#xff1a;https://www.cornerstonejs.org/ 在线查看显示效果&#xff08;加载需时间&#xff0c;可先点击运行&#xff09;&#xff0c;欢迎fork 二、代码示例 了解了Cornerstone是什么&#xff0c;有什么作用后&…

解决elementUI或elementPlus的按钮点击后需要失去焦点才能恢复原本样式问题

废话不多说直接上代码&#xff0c;只需要在button中添加如下代码即可 focus"(e) > e.target.blur()"

精确掌控并发:分布式环境下并发流量控制的设计与实现(二)

3. 固定窗口 参考&#xff1a;精确掌控并发&#xff1a;分布式环境下并发流量控制的设计与实现&#xff08;一&#xff09;-CSDN博客 4. 滑动窗口 滑动窗口算法是一种更为灵活的流量控制方案&#xff0c;它比固定窗口算法能更平滑地处理突发流量。在滑动窗口中&#xff0c;时…

什么是云服务器ECS及其优势、购买、使用方式和部署建议

阿里云服务器ECS英文全程Elastic Compute Service&#xff0c;云服务器ECS是一种安全可靠、弹性可伸缩的云计算服务&#xff0c;阿里云提供多种云服务器ECS实例规格&#xff0c;如经济型e实例、通用算力型u1、ECS计算型c7、通用型g7、GPU实例等&#xff0c;阿里云百科aliyunbai…

UE5 通过接口实现角色描边效果

接口不能够被实例化&#xff0c;不能够在内部书写函数的逻辑和设置属性&#xff0c;只能够被继承使用。它能够让不同的类实现有相同的函数&#xff0c;继承接口的类必须实现接口的函数。 并且&#xff0c;我们可以在不同的类里面的函数实现也不同&#xff0c;比如A类描边是红色…

Windows 项目从0到1的部署

目录 一. 安装jdk 1.1 安装jdk 1.2 配置jdk的环境配置jdk 1.3 配置成功 二. 配置tomcat 2.1 启动tomcat 2.2 防火墙设置 三. 安装MySQL 3.1 安装步骤 3.2 内部连接 3.3 外部连接 四. 部署项目 4.1 项目部署 4.2 修改mysql的用户密码 一. 安装jdk 这里给大家准备好了jdk和…

Chrome DevTools 常用面板攻略

文章目录 前言一、概述1.1 简介1.2 DevTools 初步了解 二、设备模式&#xff08;Device Mode&#xff09;2.1 面板概述2.2 设备切换 三、元素面板&#xff08;Elements&#xff09;3.1 面板概述3.2 编辑样式3.2.1 添加、启用和停用 CSS 类3.2.2 添加或移除动态样式3.2.3 快速向…

YOLOv5改进 | 检测头篇 | ASFFHead自适应空间特征融合检测头(全网首发)

一、本文介绍 本文给大家带来的改进机制是利用ASFF改进YOLOv5的检测头形成新的检测头Detect_ASFF,其主要创新是引入了一种自适应的空间特征融合方式,有效地过滤掉冲突信息,从而增强了尺度不变性。经过我的实验验证,修改后的检测头在所有的检测目标上均有大幅度的涨点效果,…

【数据库原理】(24)数据库安全性策略

数据库安全性是数据库管理系统&#xff08;DBMS&#xff09;中一个至关重要的方面。它指的是保护数据库免受非授权访问和恶意操作&#xff0c;包括数据泄露、修改、破坏等。 多层安全模型 在典型的计算机系统安全模型中&#xff0c;安全措施被设置在不同层级&#xff1a; 应用…

将.NET应用转换成Window服务

写在前面 本文介绍了将.NET8.0应用程序转换成Windows服务。 需要在NuGet中获取并安装&#xff1a;Microsoft.Extensions.Hosting.WindowsServices 包 代码实现 using System.Runtime.InteropServices; using WorkerService1;public class Program {public static void Main…

Vue+ElementUI+Axios实现携带参数的文件上传(数据校验+进度条)

VueElementUIAxios实现携带参数的文件上传&#xff08;数据校验进度条&#xff09; 可以实现对上传文件的类型&#xff0c;大小进行数据校验&#xff0c;以及对上传文件所要携带的数据也进行的校验&#xff0c;也有文件上传进度的进度条。 一、Vue 结构部分 弹窗显示&#xff0…

Jenkins 问题

从gitlab 仓库拉去代码到Jenkins本地报错 ERROR: Couldn’t find any revision to build. Verify the repository and branch configuration for this job. 问题原因&#xff1a; 创建条目》配置的时候&#xff0c;gitlab仓库不存在master分支 修复后&#xff1a;

[每周一更]-(第82期):认识自然处理语言(NLP)

GPT的大火&#xff0c;带起了行业内大模型的爆发&#xff1b;国内外都开始拥有或者研发自己的大模型&#xff0c;下边我们从NLP来进一步深入了解大模型、AI。 一、什么是NLP&#xff1f; 自然语言处理&#xff08;英语&#xff1a;Natural Language Processing&#xff0c;缩…

2000年第五次人口普查数据,shp/excel格式均有,划分年龄段、性别占比等字段

基本信息. 数据名称: 第五次人口普查数据 数据格式: Shp、excel 数据时间: 2000年 数据几何类型: 面 数据坐标系: WGS84坐标系 数据来源&#xff1a;第五次人口普查数据 数据字段&#xff1a; 序号字段名称字段说明1a2000_zrks2000年_常住人口&#xff08;人&…

Pycharm close project 速度缓慢解决办法

解决Pycharm close project缓慢现象 1.问题描述 close project后需要等待很长的时间。 2.解决办法 在Help -> Find Action -> 输入 Registry -> 禁用ide.await.scope.completion 问题解决&#xff01;&#xff01;&#xff01; &#x1f603;&#x1f603;&#x…

C++(10)——模板

目录 1.什么是泛式编程以及模板的引入&#xff1a; 2. 模板&#xff1a; 2.1 函数模板&#xff1a; 2.2 类模板&#xff1a; 1.什么是泛式编程以及模板的引入&#xff1a; 在之前排序的部分中&#xff0c;为了完成某个特定功能&#xff0c;经常会用到交换函数&#xff0c;即…