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 bytes
	void* 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_FINALLY
		return;
}

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!";
	else
		return ((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()分配的内存空间。


🚩总结

请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flutter笔记:使用Flutter私有类涉及的授权协议问题

Flutter笔记 使用Flutter私有类涉及的授权协议问题 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.cs…

202012青少年软件编程(Python)等级考试试卷(一级)

第 1 题 【单选题】 运行下方代码段&#xff0c;输出是6&#xff0c;则输入的可能是&#xff08; &#xff09;。 a eval(input())print(a)A :8%2 B :8/2 C :3*2 D :3**2 正确答案:C 试题解析: 第 2 题 【单选题】 关于Python变量&#xff0c;下列叙述正确的是&#x…

中仕公考:北京额度管理(社会化/员额制)教师和在编教师的区别

一、什么是额度管理(社会化/员额制)教师? 社会化、员额制教师、额度管理教师&#xff0c;是为了解决中小学教师总量或编制不足&#xff0c;在现有编制基础上另外核定一定数额由地方政府保障&#xff0c;不纳入编制管理&#xff0c;另外实行专项管理。不办理入编手续&#xff…

自定义表单元素组件内容变化触发ElForm重新校验

对于下图中“付费类型”怎么实现有很多种方式&#xff0c;我能想到的是以下两种&#xff1a; Element Plus的RadioButton自定义组件 1. RadioButton 它本质上就是一个单选组件&#xff0c;它跟Element Plus的RadioButton本质上没有区别&#xff0c;无非是外观上的差别。那么…

Windows计算机安装并连接Linux系统(VMware、XShell)

一、Windows计算机可以通过VMware虚拟机&#xff0c;安装Linux系统 说明&#xff1a;VMware Workstation虚拟化软件&#xff0c;在Windows或Linux计算机运行的应用程序&#xff0c;可模拟基于X86的标准PC环境&#xff0c;构建的虚拟机和真实的物理主机没太大区别&#xff08;区…

自学Vue3 Day2

一、组合式Api组件通信 1.父与子之间 父传子&#xff1a;父导入子组件&#xff0c;定义好数据&#xff0c;子组件用props接收&#xff0c;这里defineProps底层本质还是props. 注意模板渲染过程不需要写props 子传 父&#xff1a; 2.模版引用&#xff08;ref&#xff09;和组…

MySQL:设置唯一索引还是出现重复数据

一、MySQL中null和null不相等 MySQL中&#xff1a;两个值比较会出现&#xff1a;true、false、null 三种情况&#xff1b; null和null相比较会出现未知的类型 二、然后看完这个视频 美团二面&#xff1a;我记得明明加了mysql唯一索引&#xff0c;为啥还会出现重复数据吗&…

PHP的数组练习实验

实 验 目 的 掌握索引和关联数组&#xff0c;以及下标和元素概念&#xff1b; 掌握数组创建、初始化&#xff0c;以及元素添加、删除、修改操作&#xff1b; 掌握foreach作用、语法、执行过程和使用&#xff1b; 能应用数组输出表格和数据。 任务1&#xff1a;使用一维索引数…

GPT是什么?直观解释Transformer | 深度学习第5章 【3Blue1Brown 官方双语】

【官方双语】GPT是什么&#xff1f;直观解释Transformer | 深度学习第5章 0:00 - 预测&#xff0c;采样&#xff0c;重复&#xff1a;预训练/生成式/Transformer模型 3:03 - Transformer 的内部结构 6:36 - 本期总述 7:20 - 深度学习的大框架 12:27 - GPT的第一层&#xff1a;…

(一)JSP教程——JSP脚本标签

JSP脚本标签 JSP脚本标签通常用作对象操作和数据运算&#xff0c;从而动态地生成页面内容。这里有三种类型的脚本标签&#xff1a;声明、代码段和表达式。 JSP声明 JSP声明一个或多个变量、方法&#xff0c;供以后的代码使用。必须先对变量和方法进行声明&#xff0c;才能使用…

golang学习笔记(内存逃逸分析)

golang的内存逃逸 逃逸分析&#xff08; Escape analysis&#xff09; 是指由编译器决定内存分配的位置&#xff0c; 不需要程序员指定。 函数中申请一个新的对象。 如果分配在栈中&#xff0c; 则函数执行结束可自动将内存回收&#xff1b;如果分配在堆中&#xff0c; 则函数…

微软开源 MS-DOS「GitHub 热点速览」

上周又是被「大模型」霸榜的一周&#xff0c;各种 AI、LLM、ChatGPT、Sora、RAG 的开源项目在 GitHub 上“争相斗艳”。这不 Meta 刚开源 Llama 3 没几天&#xff0c;苹果紧跟着就开源了手机端大模型&#xff1a;CoreNet。 GitHub 地址&#xff1a;github.com/apple/corenet 开…

网络安全前置知识-linux操作系统

计算机体系结构 计算机发展历史 计算机组成 计算机硬件组成 1. CPU 原文链接&#xff1a;https://blog.csdn.net/stone_fall/article/details/88414017 一条指令的执行过程分为以下5个周期&#xff1a; 取指令周期&#xff08;Instruction Fetch&#xff0c;IF&#xff…

【Cpp】类和对象

标题&#xff1a;【Cpp】类和对象 水墨不写bug 正文开始&#xff1a; &#xff08;一&#xff09;面向过程与面向对象 面向过程和面向对象是两种不同的编程思想。 面向过程指的是将程序分解成多个步骤&#xff0c;每个步骤都是一个独立的函数&#xff0c;通过函数之间的调用实…

使用OneAPI创建LLM访问API Key

OneAPI 是一个能替代 springdoc-openapi/Swagger 的 API 生产工具&#xff0c;既不需要修改后端代码&#xff0c;也不需要启动应用。 也支持导出 OpenAPI 3.0 协议数据&#xff0c;方便在其他工具中消费。 前面我们介绍了LobeChat需要配置LLM的API访问Key&#xff0c;今天你可…

《架构即未来》读后感

目录 一、引言 二、《架构即未来》读后感 1、主题的简要介绍 2、我的看法和理解 3、作者的优点和传递的信息 4、思想如何适用于当今社会 三、《架构即未来》对于企业发展的影响具体体现在哪些方面&#xff1f; 一、引言 任何一个持续成长的公司最终都需要解决系统、组织…

【讲解下如何解决一些常见的 Composer 错误】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

【Spring AI】09. ETL 管道

文章目录 ETL PipelineAPI 概述入门指南ETL 接口和实现DocumentReaderJsonReaderTextReaderPagePdfDocumentReaderParagraphPdfDocumentReaderTikaDocumentReader DocumentTransformerTextSplitterTokenTextSplitterContentFormatTransformerKeywordMetadataEnricherSummaryMet…

Ftrans文件外发系统 构建安全可控文件外发流程

文件外发系统是企业数据安全管理中的关键组成部分&#xff0c;它主要用于处理企业内部文件向外部传输的流程&#xff0c;确保数据在合法、安全、可控的前提下进行外发。 文件外发系统的主要作用包括&#xff1a; 1、防止数据泄露&#xff1a;通过严格的审批流程和安全策略&…

【强训笔记】day7

NO.1 思路&#xff1a;双指针模拟&#xff0c;begin表示最长数字字符串最后一个字符&#xff0c;而len表示数字字符串的长度&#xff0c;i用来遍历&#xff0c;如果为数字&#xff0c;那么定义j变量继续遍历&#xff0c;直到不为数字&#xff0c;i-j如果大于len&#xff0c;就…