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

(注:本文是针对上篇文章中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);  __TRYpHead = pHdr(pUserData);_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));_free_dbg( pUserData, pHead->nBlockUse );__FINALLY_munlock(_HEAP_LOCK); __END_TRY_FINALLYreturn;
}#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.mzph.cn/news/626383.shtml

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

相关文章

实战 php 使用 wkhtmltopdf 生成pdf的全过程

公司里边有生成pdf报告的业务需求,之前有过尝试用tcpdf,直接生成的pdf的过程,但是pdf报告的内容数据,根据不同内容的变化,都是各种各样的bug,一直处理修修补补的状态,让后台开发人员很是头疼. 经过思索和甄选,总结出我们的业务中是由于样式不可控导致的,当时从逻辑上就思考到用…

医院如何选择高效的内外网数据交换方案 替代U盘进行跨网传输?

医院信息网络是所有网络中安全性要求较高的网络之一&#xff0c;因此很多医院基于信息安全相关要求&#xff0c;会使用防火墙将网络隔离成内网和外网。内网用于日常医疗信息交换&#xff0c;外网可以及时获取Internet信息资源。但是网络隔离后&#xff0c;医院仍存在将报告资料…

原子类-数组类型原子类

数组类型原子类 AtomicIntegerArray:整型数组原子类 AtomicLongrArray:长整型数组原子类 AtomicReferenceArray:用类型数组原子类 常用API简介 数组类型原子类常用API简介 public final int get(int i) //获取 index=i 位置元素的值 public final int getAndSet(int i, in…

[数据集][目标检测]茶叶病害数据集VOC+YOLO格式883张8类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;883 标注数量(xml文件个数)&#xff1a;883 标注数量(txt文件个数)&#xff1a;883 标注类别…

哪些药物可能对发作性睡病有帮助?

发作性睡病是一种慢性睡眠障碍&#xff0c;其症状包括不可抗拒的短期睡眠发作、猝倒、睡眠麻痹和睡眠幻觉等。治疗发作性睡病的方法包括药物治疗和非药物治疗&#xff0c;其中药物治疗是重要的手段之一。 目前治疗发作性睡病的药物主要包括中枢兴奋剂、抗抑郁药和镇静催眠药等…

【NI国产替代】PXI-6254,32 AI(16位,1 MS/s),48 DIO,PXI多功能I/O模块

32 AI&#xff08;16位&#xff0c;1 MS/s&#xff09;&#xff0c;48 DIO&#xff0c;PXI多功能I/O模块 PXI-6254提供模拟输入、关联数字I/O、两个32位计数器/定时器以及模拟和数字触发。该设备为从实验室自动化、研究、设计验证/测试到制造测试等各种应用提供了低成本的可靠D…

Mybatis基础---------增删查改

增删改 1、新建工具类用来获取会话对象 import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.apache.ibatis.io.Resources;import java.io.IOExcept…

Java运算符作用及解析

Java运算符是对变量或者常量进行操作的符号。以下是Java中常见运算符的解析&#xff1a; 赋值运算符&#xff1a;如“”&#xff0c;将右侧的值赋给左侧的变量。一元运算符&#xff1a;如“”“-”“!”&#xff0c;用于对变量进行操作。算术运算符&#xff1a;如“”“-”“*…

典型场景解析|PolarDB分布式版如何支撑SaaS多租户?

SaaS多租户背景 很多平台类应用或系统&#xff08;如电商CRM平台、仓库订单平台等等&#xff09;&#xff0c;它们的服务模型是围绕用户维度&#xff08;这里的用户维度可以是一个卖家或品牌&#xff0c;可以是一个仓库等&#xff09;展开的。因此&#xff0c;这类型的平台业务…

【原创】docker +宝塔+安装zabbix

Zabbix: Zabbix可以监控各种网络服务、服务器和网络设备&#xff0c;而无需在目标设备上安装客户端。它的强大之处在于自带的Web界面&#xff0c;能够提供实时监控和各种报警功能。方法1&#xff1a; 步骤 创建Docker Compose文件: 首先&#xff0c;你需要创建一个docker-comp…

C技能树-学习笔记(1-2)C语言概述和数据类型

参考&#xff1a;https://edu.csdn.net/skill/c 1、输出 “Hello, World!” 字符串&#xff0c;请选出错误答案。 2、错误的print函数。 for … in …&#xff1a;是python的语法&#xff0c;C语言的写法是for (;&#x1f609; 3、C标准 没有C19标准。 4、了解C编译管道 …

AI嵌入式K210项目(4)-FPIOA

文章目录 前言一、FPIOA是什么&#xff1f;二、FPIOA代码分析总结 前言 磨刀不误砍柴工&#xff0c;在正式开始学习之前&#xff0c;我们先来了解下K210自带的FPIOA&#xff0c;这个概念可能与我们之前学习STM32有很多不同&#xff0c;STM32每个引脚都有特定的功能&#xff0c…

嵌入式培训机构四个月实训课程笔记(完整版)-C++和QT编程第三天-C++类和对象高级应用(物联技术666)

链接:https://pan.baidu.com/s/1YRXI0WiABUlYaQXQDNfbyA?pwd=1688 提取码:1688 上午:类和对象高级应用(续) 下午:派生和继承 教学内容: 1、友元 类的私有成员只能在类定义的范围内使用,也就是说私有成员只能通过它的成员函数来访问但是,有时候需要在类的外部访问…

关于前端面试中forEach方法的灵魂7问?

目录 前言 一、forEach方法支持处理异步函数吗&#xff1f; 二、forEach方法在循环过程中能中断吗&#xff1f; 三、forEach 在删除自己的元素后能重置索引吗&#xff1f; 四、forEach 的性能相比for循环哪个好&#xff1f; 五、使用 forEach 会不会改变原来的数组&#…

xshell:关于ssh用户身份验证不能选择password的解决方法

接下来我将告诉大家如何进行修改让其能够进行密码登录 我使用的软件是VM VirtualBox管理器 进行用户名密码登录后 输入 cd /etc/ 切换到etc目录下 cd /etc/ 切换到etc目录后输入ls ls 切换到ssh目录下 cd ssh 进入文件 sshd_config vi sshd_config 找到指定部分进行修改 如何…

多级缓存架构(三)OpenResty Lua缓存

文章目录 一、nginx服务二、OpenResty服务1. 服务块定义2. 配置修改3. Lua程序编写4. 总结 三、运行四、测试五、高可用集群1. openresty2. tomcat 通过本文章&#xff0c;可以完成多级缓存架构中的Lua缓存。 一、nginx服务 在docker/docker-compose.yml中添加nginx服务块。…

评估文字识别准确性的方法与流程

随着信息技术的发展&#xff0c;文字识别技术在各个领域得到了广泛的应用。然而&#xff0c;在实际应用中&#xff0c;如何评估文字识别的准确性&#xff0c;一直是相关领域的一个难题。本文将介绍几种常用的文字识别准确性评估方法&#xff0c;以期为相关领域的研究提供参考。…

使用vite框架封装vue3插件,发布到npm

目录 一、vue环境搭建 1、创建App.vue 2、修改main.ts 3、修改vite.config.ts 二、插件配置 1、创建插件 2、开发调试 3、打包配置 4、package.json文件配置 上一篇文章讲述使用vite《如何使用vite框架封装一个js库&#xff0c;并发布npm包》封装js库&#xff0c;本文将…

Memory Deduplication Attacks

原文 最近看到了一系列描述Memory Deduplication Attacks的研究&#xff0c;它已被用于指纹系统[1]、破解 (K)ASLR[2,3,4]、泄漏数据库记录[4]&#xff0c;甚至利用 rowhammer[ 5]。这是一类非常酷的攻击&#xff0c;以前从未听说过&#xff0c;但我没有太多运气找到这些攻击的…

Jmeter后置处理器——JSON提取器

目录 1、简介 2、使用步骤 1&#xff09;添加线程组 2&#xff09;添加http请求 3&#xff09; 添加JSON提取器 1、简介 JSON是一种简单的数据交换格式&#xff0c;允许互联网应用程序快速传输数据。JSON提取器可以从JSON格式响应数据中提取数据、简化从JSON原始数据中提取特定…