C++奇迹之旅:C++内存管理的机制(进阶篇)

请添加图片描述

文章目录

  • 📝new和delete操作自定义类型
  • 🌠 operator new与operator delete函数
    • 🌉operator new与operator delete函数
  • 🌠new和delete的实现原理
    • 🌉内置类型
    • 🌉自定义类型
  • 🌠定位new表达式(placement-new)
  • 🚩总结


📝new和delete操作自定义类型

我们先看mallocfree,调试可以发现并不会调用析构函数

class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};int main()
{A* p1 = (A*)malloc(sizeof(A));free(p1);return 0;
}

在这里插入图片描述
再看newdelete

A* p2 = new A(1);
delete p2;

在这里插入图片描述
总结:
new/deletemalloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
而对于内置类型几乎是一样的

int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int;
free(p3);
delete p4;

这是汇编一览图:
此时多出来了一个operator new这是什么,为什么new会去调用operator new

00482C36    call   operator new(048114Ah )

在这里插入图片描述

🌠 operator new与operator delete函数

🌉operator new与operator delete函数

newdelete是用户进行动态内存申请和释放的操作符,operator newoperator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid* 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);
}

operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。

// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;

operator delete: 该函数最终是通过free来释放空间的

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);__FINALLY_munlock(_HEAP_LOCK); /* release other threads */__END_TRY_FINALLYreturn;
}

free的实现

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

如抛异常例子:

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0)throw "Division by zero condition!";elsereturn ((double)a / (double)b);
}void Func()
{int len, time;cin >> len >> time;cout << Division(len, time) << endl;
}int main()
{try {Func();}catch(const char* errmsg){//当 Division(len, time) 函数抛出这种异常时,异常对象会被赋值给 errmsg 变量。
//然后,这个 catch 块会输出 errmsg 的内容,即 "Division by zero condition!"cout << errmsg << endl;}catch (...){//这个 catch 块用于捕获任何其他类型的未知异常。
//当 try 块中发生任何其他类型的异常时,这个 catch 块会被执行。
//它会输出 "unkown exception"。cout << "unkown exception" << endl;}return 0;
}

当你输入两个数让b == 0时程序抛异常,抛出"Division by zero condition!"他不会再回到Func()函数中的cout << Division(len, time) << endl;而是会跳到catch(const char* errmsg)中,异常对象会被赋值给 errmsg 变量。然后,这个 catch 块会输出 errmsg 的内容,即 "Division by zero condition!",程序结束。

总结:
通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

🌠new和delete的实现原理

🌉内置类型

如果申请的是内置类型的空间,newmallocdeletefree基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL

🌉自定义类型

new的原理

  1. 调用operator new函数申请空间
  2. 在申请的空间上执行构造函数,完成对象的构造
    在这里插入图片描述
    delete的原理
  3. 在空间上执行析构函数,完成对象中资源的清理工作
  4. 调用operator delete函数释放对象的空间
class Stack 
{
public:// 构造函数Stack(int initialCapacity = 10) : _a(new int[initialCapacity]), _top(0), _capacity(initialCapacity) {}// 析构函数~Stack() {delete[] _a;}// 压入元素void push(int value) {// 如果栈已满,需要扩容if (_top == _capacity) {resize(2 * _capacity);}// 将元素压入栈顶_a[_top++] = value;}// 弹出栈顶元素int pop() {// 如果栈为空,抛出异常if (_top == 0) {throw runtime_error("Stack is empty");}// 弹出栈顶元素并返回return _a[--_top];}// 判断栈是否为空bool empty() const {return _top == 0;}// 返回栈中元素的个数int size() const {return _top;}private:// 扩容函数,分配新的数组空间并移动元素void resize(int newCapacity) {// 分配新的数组空间int* newArray = new int[newCapacity];// 使用 std::move 将原数组中的元素移动到新数组中move(_a, _a + _top, newArray);// 释放原数组空间delete[] _a;// 更新数组指针和容量_a = newArray;_capacity = newCapacity;}// 存储元素的数组指针int* _a;// 栈顶元素的索引int _top;// 数组的容量int _capacity;
};int main() 
{// 创建一个栈Stack* p3 = new Stack;// 释放栈的内存delete p3; 压入三个元素//p3->push(1);//p3->push(2);//p3->push(3); 依次弹出并打印元素//while (!p3->empty()) //{//    cout << p3->pop() << " ";//}//cout << endl;return 0;
}

在这里插入图片描述

先析构_a指向的空间,再释放p3指向的空间

new T[N]的原理

  1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申
  2. 在申请的空间上执行N次构造函数

delete[]的原理
3. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
4. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};int main()
{A* p1 = new A;A* p2 = new A[10];delete p1;delete[] p2;return 0;
}

在这里插入图片描述
A类只有一个4字节大小的int成员变量。那么,创建一个A类型的对象应该需要4字节的内存空间。A* p2 = new A[10];这我们动态创建了一个包含10个A对象的数组。10 * 4 = 40 bytes,为什么是44bite呢?
在动态分配数组内存时,编译器通常会在实际的数组内存之前分配一些额外的空间,用于存储数组的元素个数等信息。这样做的目的是为了在执行delete[]操作时,能够正确地调用所有元素的析构函数。

总结:都开4byte存储对象个数,方便delete[]时,知道有多少个对象,要调用多少次析构函数

在这里插入图片描述
内置类型就没有额外开空间:
因为,内置类型已经固定好,无需调用析构函数

int* p3 = new int[10];
delete[] p3;

在这里插入图片描述

🌠定位new表达式(placement-new)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:

new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表

使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};// 定位new/replacement new
int main()
{// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行A* p1 = (A*)malloc(sizeof(A));new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参p1->~A();free(p1);A* p2 = (A*)operator new(sizeof(A));new(p2)A(10);p2->~A();operator delete(p2);return 0;
}
 A* p1 = (A*)malloc(sizeof(A));

使用malloc()函数分配了一块与A类对象大小相同的内存空间,但此时p1指向的只是一块内存空间,还不是一个真正的A对象,因为A的构造函数还没有被调用。

new(p1)A;

使用"定位new"的语法在已分配的内存空间上构造一个A对象。如果A类的构造函数有参数,需要在这里传入参数,例如new(p2)A(10);

 p1->~A();

显式地调用A对象的析构函数,释放对象占用的资源。

 free(p1);

free()函数释放之前使用malloc()分配的内存空间。


🚩总结

请添加图片描述

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

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

相关文章

Python 全栈体系【四阶】(三十八)

第五章 深度学习 八、目标检测 3. 目标检测模型 3.2 YOLO 系列 3.2.1 YOLOv1&#xff08;2016&#xff09; 3.2.1.1 基本思想 YOLO&#xff08;You Only Look Once &#xff09;是继 RCNN&#xff0c;fast-RCNN 和 faster-RCNN 之后&#xff0c;Ross Girshick 针对 DL 目…

【牛客网】值周

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 差分。 因为l<100000000,所以数组开1e8。 唯一需要注意的点就是前面给b[0]单独赋值为1&#xff08;因为如果在循环中给b[0]赋值&…

Docker Compose如何安装

Docker Compose的安装通常依赖于你的操作系统。以下是在不同操作系统中安装Docker Compose的方法&#xff1a; Linux 系统 //下载最新版本的Docker Compose sudo curl -L "https://github.com/docker/compose/releases/download/v2.5.1/docker-compose-$(uname -s)-$(un…

算法训练营第十天 | LeetCode 232 用栈实现队列、LeetCode 225 用队列实现栈

栈的实现有顺序表和链式表两种&#xff0c;也就是数组和链表实现。 其中抽象栈类的私有成员函数有operator的重载函数和stack的构造函数&#xff0c;为了保护栈的构造和拷贝被保护。公有成员函数有Stack()&#xff0c;~Stack()&#xff0c;clear()&#xff0c;push()&#xff…

修复提高PDF清晰度软件

修复提高PDF清晰度软件 使用python脚本对pdf进行优化&#xff0c;提高pdf清晰度&#xff0c;使文字更加清晰&#xff0c;观感更佳。仅适用黑白扫描版pdf&#xff0c;且文字较为清晰&#xff0c;若字形笔画较模糊会更加模糊。 注意事项 cpu满核极速运行&#xff0c;软件可能卡…

【实时数仓架构】方法论

笔者不是专业的实时数仓架构&#xff0c;这是笔者从其他人经验和网上资料整理而来&#xff0c;仅供参考。写此文章意义&#xff0c;加深对实时数仓理解。 一、实时数仓架构技术演进 1.1 四种架构演进 1&#xff09;离线大数据架构 一种批处理离线数据分析架构&#xff0c;…

【Java从入门到精通】Java 正则表达式

目录 正则表达式实例 &#x1f349;java.util.regex 包 &#x1f349;实例 &#x1f349;捕获组 &#x1f349;实例 &#x1f349;RegexMatches.java 文件代码&#xff1a; &#x1f349;正则表达式语法 &#x1f349;Matcher 类的方法 &#x1f349;索引方法 &#…

[XR806开发板试用] XR806 调用cjson 实现数据序列化

很荣幸获得极术设区提供的这次试用机会&#xff0c;可以接触鸿蒙操作系统。我工作接触最多的是linux 平台的嵌入式ARM平台较多&#xff0c;这次跑了下鸿蒙&#xff0c;也非常有趣。 不过接进年底了&#xff0c;日常大小琐碎事情突然多了起来&#xff0c;测评的比较匆忙&#x…

【工具类安装教程】IDEA Ui设计器JFormDesigner

1、下载插件 File->Settings->Plugins->JFormDesigner 2、注册教程 2.1注册机下载 链接&#xff1a;https://pan.baidu.com/s/1Rb1EMva5HIYbyBcYgCxIsw 提取码&#xff1a;6666 2.2找到目录 1、找到idea目录 2.3进入plugins目录 找到在JFormDesigner文件夹下lib文…

政安晨:【Keras机器学习示例演绎】(二十九)—— 利用卷积 LSTM 进行下一帧视频预测

目录 简介 设置 数据集构建 数据可视化 模型构建 模型训练 帧预测可视化 预测视频 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实战 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&…

【R语言】描述性数据分析与数据可视化

我们处理的变量可以分为两类&#xff0c;一类是连续型变量&#xff0c;另一类叫做分类型变量&#xff0c;其中对于连续型变量&#xff0c;如果服从正态分布就用平均值填充NA&#xff0c;不服从正态分布就用中位数填充NA&#xff0c;对于分类型变量&#xff0c;不管是有序的&…

IOS上线操作

1、拥有苹果开发者账号 2、配置证书&#xff0c;进入苹果开发者官网&#xff08;https://developer.apple.com/&#xff09; 3、点击账户&#xff08;account&#xff09;&#xff0c;然后创建一个唯一的标识符 4、点击"Identifiers"&#xff0c;然后点击"&qu…

GEE必须会教程—一文教你华南地区NDVI趋势可视化分析(代码分享)

本期将带来NDVI的变化趋势分析&#xff0c;一个地区究竟是变绿了&#xff0c;还是植被退化了&#xff0c;如何进行量化呢&#xff1f;小编给出了代码&#xff0c;后期将带来详细的解释&#xff0c;大家可以先尝试着分析一下代码。 上代码&#xff01; var regionee.FeatureCo…

RCE学习

从最近的xyctf中&#xff0c;最大的感受就是自己的rce基础并不牢固&#xff0c;所以马上来恶补一下 漏洞成因 php和其他语言有很多能够执行系统命令或执行其他php代码的函数&#xff0c;因为开发者的使用不当&#xff0c;使得用户能够控制传递给执行命令的函数的参数&#xf…

AI图书推荐:用ChatGPT快速创建在线课程

您是否是您领域的专家&#xff0c;拥有丰富的知识和技能可以分享&#xff1f;您是否曾想过创建一个在线课程&#xff0c;但被这个过程吓倒了&#xff1f;那么&#xff0c;是时候把这些担忧放在一边&#xff0c;迈出这一步了&#xff01;有了这本指南和ChatGPT的帮助&#xff0c…

设计模式: 模板模式

目录 一&#xff0c;模板模式 二&#xff0c;特点 三&#xff0c;组成部分 四&#xff0c;实现步骤 五&#xff0c;案例 一&#xff0c;模板模式 模板模式&#xff08;Template Pattern&#xff09;是一种行为型设计模式&#xff0c;它在超类中定义了一个算法的骨架&#…

Node.js -- mongoose

文章目录 1. 介绍2. mongoose 连接数据库3. 插入文件4. 字段类型5. 字段值验证6. 文档处理6.1 删除文档6.2 更新文档6.3 读取文档 7. 条件控制8. 个性化读取9. 代码模块化 1. 介绍 Mongoose是一个对象文档模型库&#xff0c;官网http://www.mongoosejs.net/ 方便使用代码操作mo…

“Unite“ > MacOS下很不错的网站转应用App的工具

前言 前不久在浏览mac论坛&#xff0c;无意了解到一款非常好的工具&#xff0c;可以将网站转换为app&#xff0c;考虑到我们现在的主要应用都从本地客户端转成web形式使用&#xff0c;但基于本能的使用习惯&#xff0c;还是希望有个快捷的访问信息&#xff0c;这个应用非常适合…

数组删除元素

数组删除元素 1.利用新的数组 将原数组arr的元素&#xff0c;复制到新数组newArr中&#xff0c;复制过程中将要删除的元素&#xff0c;选择不复制 public class Test01{public static void main(String [] args){String [] arr {"zhangsan","lisi","…

C语言——操作符保姆级教学(含整形提升及算数转换)

操作符 一.操作符的分类二.原码、反码、补码三.移位操作符1.左移操作符&#xff1a;<<2.右移操作符&#xff1a;>> 四.位操作符1.按位与—— &2.按位或—— |3.按位异或—— ^4.按位取反—— ~ 五.逗号表达式六.条件操作符七.操作符的属性&#xff1a;优先级、…