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.hqwc.cn/news/343719.html

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

相关文章

Maven之自定义archetype生成项目骨架(构建脚手架项目)

pom引入 <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-resources-plugin</artifactId><version>3.1.0</version><configuration><encoding>UTF-8</encoding…

python爬虫实战(8)--获取虎pu热榜

1. 需要的类库 import requests from bs4 import BeautifulSoup import pandas as pd2. 请求地址 def fetch_data():url "https://bbs.xxx.com/" # Replace with the actual base URLresponse requests.get(url)if response.status_code 200:return response.c…

Javaweb之SpringBootWeb案例查询部门以及前后端联调的详细解析

2.1 查询部门 2.1.1 原型和需求 查询的部门的信息&#xff1a;部门ID、部门名称、修改时间 通过页面原型以及需求描述&#xff0c;我们可以看到&#xff0c;部门查询&#xff0c;是不需要考虑分页操作的。 2.1.2 接口文档 部门列表查询 基本信息 请求路径&#xff1a;/depts …

关于Python里xlwings库对Excel表格的操作(三十一)

这篇小笔记主要记录如何【如何使用“Chart类”、“Api类"和“Axes函数”设置绘图区外框线型、颜色、粗细及填充颜色】。前面的小笔记已整理成目录&#xff0c;可点链接去目录寻找所需更方便。 【目录部分内容如下】【点击此处可进入目录】 &#xff08;1&#xff09;如何安…

J3-DenseNet实战

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 目录 环境步骤环境设置数据准备图像信息查看 模型构建模型训练模型效果展示 总结与心得体会 环境 系统: Linux语言: Python3.8.10深度学习…

【面试突击】网关系统面试实战

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术 的推送 发送 资料 可领取 深入理…

【LeetCode】206. 反转链表(简单)——代码随想录算法训练营Day01

题目链接&#xff1a;206. 反转链表 题目描述 206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 示例 2&#xff1a; 输入&#xff1…

Linux tail命令详解和高级用法举例

目 录 一、概述 二、tail命令解释 1&#xff0e;命令格式; 2&#xff0e;功能 3&#xff0e;选项 4&#xff0e;选项的基本用法 &#xff08;1&#xff09; 显示行号 &#xff08;2&#xff09;忽略指定字符数 &#xff08;3&#xff09; 不显示文件名 三…

leetcode 动态规划(爬楼梯、零钱兑换、完全平方数)

70. 爬楼梯&#xff08;进阶版&#xff09; 卡码网&#xff1a;57. 爬楼梯(opens new window) 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬至多m (1 < m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 注意&#xff1a;给定 n 是一个正…

UTONMOS:探索元宇宙,开启未来游戏新篇章

在元宇宙的世界里&#xff0c;游戏不再只是消遣&#xff0c;而是一个全新的互动世界&#xff0c;等待你来探索&#xff01; 逼真的虚拟现实技术&#xff0c;让你沉浸在充满想象力的游戏世界中&#xff0c;体验前所未有的刺激和乐趣。 与来自全球的玩家互动交流&#xff0c;结…

CSS 中的伪装大师:伪类和伪元素的奇妙世界

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

C语言基础语法跟练 day3

31、不使用累计乘法的基础上&#xff0c;通过移位运算&#xff08;<<&#xff09;实现2的n次方的计算。 #include <stdio.h> int main() {int i 0;scanf("%d",&i);printf("%d",1<<i);return 0; } 32、问题&#xff1a;一年约有 3.…