C++内存管理篇

文章目录

  • 1. C/C++内存分布
  • 2. C++中的内存管理方式
  • 3. operator new和operator delete函数
  • 4. new和delete的实现原理
  • 5. 定位new表达式(placement-new)

1. C/C++内存分布


C语言中,为了方便管理内存空间,将内存分成了不同的区域,每个区域管理不同的数据

C++中,保持着跟C语言一样的内存区域划分

在这里插入图片描述

int main() 
{
	int a = 0; 
	const int b = 1; 
    //很多人有个误区,认为被const修饰的变量在常量区 
    //实际上,被const修饰的变量叫做常变量,它本质还是个变量,只不过有了常属性 
    //因此b是在栈区上的 
	cout << &a << endl; 
	cout << &b << endl; 

	char arr1[] = "abcd"; 
	const char* arr2 = "abcd"; 

	arr1[0]++; 
	//arr2[0]++; 
	((char*)arr2)[0]++; 
    //即使将arr2强转成char*,程序在运行的过程中也会崩溃,暴力修改常量区的数据 

	return 0; 
}

2. C++中的内存管理方式


C++语言中,我们用malloc/calloc/realloc/free函数进行内存的开辟和释放,对内存进行管理

C++中,这些函数依然可用,但在使用上是比较麻烦的;因此C++中增加了new和delete操作符,使用这两个操作符进行内存的管理

  • C语言使用的是函数进行内存管理
  • C++中new和delete是操作符,不是函数
int main()
{
	//申请一个int类型的空间
	int* p1 = new int;

	//申请一个int类型的空间并初始化
	int* p2 = new int(1);

	//申请一个数组
	int* p3 = new int[10];

	//C++11支持
	//申请一个数据并初始化
	int* p4 = new int[10]{ 1,2,3 };

	delete p1;
	delete p2;
	delete[] p3;
	delete[] p4;

	return 0;
}

相比于C语言中利用函数管理内存,C++中的管理内存方式有哪些好处:

  1. 用法更加简洁

  2. 可以控制初始化

  3. 对于自定义类型,开辟空间+自动调用对应的构造函数

    struct ListNode
    {
    public:
    	ListNode(int val)
    		:_val(val)
    		, _next(nullptr)
    	{}
    
    	int _val;
    	ListNode* _next;
    };
    
    int main()
    {
    	ListNode* node1 = new ListNode(1);
    	ListNode* node2 = new ListNode(2);
    	ListNode* node3 = new ListNode(3);
    
    	delete node1;
    	delete node2;
    	delete node3;
    
    	return 0;
    }
    
  4. new失败了以后抛异常,不需要手动检查

    void Func2()
    {
    	int n = 1;
    	while (1)
    	{
    		int* p = new int[1024 * 1024 * 10];
    		cout << n << "->" << p << endl;
    		n++;
    	}
    }
    
    int main()
    {
    	try
    	{
    		Func2();
    	}
    	catch (const exception& e)
    	{
    		cout << e.what() << endl;
    	}
    	return 0;
    }
    
  5. 对于自定义类型,申请空间时,new会调用对应的构造函数;delete会调用对应的析构函数

    class A
    {
    public:
    	A(int a = 0)
    		:_a(a)
    	{
    		cout << "A(int a)" << endl;
    	}
       
    	~A()
    	{
    		cout << "~A()" << endl;
    	}
       
    private:
    	int _a;
    };
    
    
int main()
{
   	A* p1 = new A(1);//调用一次构造
   	delete p1;//调用一次析构
    
	A* p2 = new A[10];//调用十次构造
	delete[] p2;//调用十次析构
    
	return 0;
}

3. operator new和operator delete函数


看到operator+操作符,第一直觉会以为这是对new和delete操作符的重载函数,但其实它们不是重载函数

实际上,这两个函数是系统提供的全局函数

在底层,new通过调用operator new函数来实现申请空间;delete通过调用operator delete函数来时间空间的释放

operator new函数最终通过malloc函数申请空间,申请成功则返回,否则抛异常

operator delete函数最终通过free函数释放空间

可以看到,new和delete的最底层还是使用的malloc和free函数,只不过是对它进行了一系列封装

4. new和delete的实现原理


在这里插入图片描述

对于内置类型:

  • 两者的效果差不多,不同的是new在申请空间失败时抛异常,malloc则返回NULL

对于自定义类型:

  • new会调用operator new函数申请空间,再在申请的空间上调用构造函数
  • delete会调用析构函数完成对象中的资源清理,再调用operator delete释放对象的空间
  • new T[n]会调用operator new[]函数,operator new[]函数会调用operator new函数申请n个对象的空间
  • delete[]会调用n次析构函数,完成n个对象中资源的清理,再调用operator delete[]函数,operator delete[]安徽念书会调用operator delete函数,释放n个对象的空间
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a;
};

int main()
{
	A* p3 = new A[10];
	delete[] p3;

	return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

当然,不是所有情况都会留出一块空间存放对象个数

内置类型不需要析构,也就不会留出空间

没有显示定义析构函数的自定义类型也不会留出空间,因为没有显示定义析构函数,就认为不需要析构函数释放对象中的资源

new和delete使用时一定要对应,否则可能会出现不确定的结果

int main()
{
	// 程序能运行,但不推荐
	int* p1 = new int(1);
	delete[] p1;//error 正确的写法:delete p1

	// 若A显示定义析构函数,程序陷入无限调用析构的死循环中
	// 若A没有显示定义析构函数,程序能运行,但不推荐
	A* p2 = new A(1);
	delete[] p2;//error 正确的写法:delete p2

	// 程序能运行,但不推荐
	int* p3 = new int[10];
	delete p3;//error 正确的写法:delete[] p3

	// 若A显示定义析构函数,程序报错,原因是释放的位置错了
	// 若A没有显示定义析构函数,程序能运行,但不推荐
	A* p4 = new A[10];
	delete p4;//error 正确的写法:delete[] p4

	return 0;
}
// C语言创建不带哨兵位的链表
typedef int DataType;

struct ListNode
{
	DataType val;
	struct ListNode* next;
};

struct ListNode* CreateNewNode(DataType val)
{
	struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
	if (newNode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newNode->next = NULL;
	newNode->val = val;
	return newNode;
}

struct ListNode* CreateList(int n)
{
	struct ListNode* head = CreateNewNode(-1);
	struct ListNode* tail = head;

	for (int i = 1; i <= n; i++)
	{
		struct ListNode* temp = CreateNewNode(i);
		tail->next = temp;
		tail = tail->next;
	}

	struct ListNode* cur = head->next;
	free(head);
	return cur;
}

int main()
{
	struct ListNode* head = CreateList(5);
	return 0;
}

struct ListNode
{
public:
	ListNode(int val)
		:_val(val)
		,_next(nullptr)
	{}

	int _val;
	ListNode* _next;
};

// C++创建不带哨兵位的链表
ListNode* CreateList(int n)
{
	ListNode head(-1);
	ListNode* tail = &head;
	int val = 0;
	printf("请依次输入值:>");
	for (int i = 1; i <= n; i++)
	{
		cin >> val;
		ListNode* newNode = new ListNode(val);
		tail->_next = newNode;
		tail = tail->_next;
	}
	return head._next;
}

int main()
{
	ListNode* list = CreateList(5);
	return 0;
}

5. 定位new表达式(placement-new)


在某些场景下,我们向内存申请的空间没有初始化,比如向内存池申请的空间,如果是自定义类型的对象,我们可以使用new的定义表达式进行显示调用构造函数进行初始化

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a;
};

int main()
{
	A* p1 = (A*)operator new(sizeof(A));
	new(p1)A(1);

	p1->~A();
	operator delete(p1);

	return 0;
}

malloc/free和new/delete的区别:

  • 共同点:都是从堆上申请空间,需要自己手动释放空间

  • 不同点:

    用法上:

    1. malloc/free是函数;new/delete是操作符
    2. malloc申请的空间不会初始化;new会初始化
    3. malloc申请空间时,需要手动计算空间大小;new只需加上空间类型即可,想创建多个对象,只需在[]里加上对象的个数
    4. malloc的返回值为void*,需要做强转处理;new的返回值就是空间类型,不需要处理
    5. malloc申请空间失败时返回NULL,因此使用时必须先判空;new不需要,但new需要捕获异常

    底层特性上:

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

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

相关文章

mysql-视图,创建表,存储过程,循环,判断实操命令

数据库操作命令在IDEA工具database的console命令 数据库表结构与视图 事务隔离级别RR&#xff08;REPEATABLE-READ&#xff09;解决不可重复读演示 mysql-RR隔离级别 -- 查询隔离级别 select transaction_isolation;-- 设置隔离级别 set session transaction isolation level …

基于Kronig-Penney能带模型的MATLAB求解与仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于Kronig-Penney能带模型的MATLAB求解与仿真.综合利用 MATLAB提供的求解常微分方程、矩阵行列式、代数表达式化简及绘图等函数 ,可使 Kronig-Penney能带模型分析…

P9238 [蓝桥杯 2023 省 A] 翻转硬币(杜教筛+莫比乌斯)

题目&#xff1a;https://www.luogu.com.cn/problem/P9238 思路&#xff1a; 代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> #include<cstring> #include<cmath> #include<ctime> #include<algo…

信息抽取在旅游行业的应用:以景点信息抽取为例

开源项目推荐 今天先给大家推荐一个开源项目&#xff0c;多模态AI能力引擎平台: 免费的自然语言处理、情感分析、实体识别、图像识别与分类、OCR识别、语音识别接口&#xff0c;功能强大&#xff0c;欢迎体验。 https://gitee.com/stonedtx/free-nlp-api 场景描述 在旅游行业…

酷开科技暖心推荐 酷开系统壁纸模式带来独特视觉享受

一款好看的壁纸能够让人眼前一亮&#xff0c;酷开科技倾心打造的酷开系统壁纸模式&#xff0c;以其独特的美学设计和视觉享受&#xff0c;为消费者提供了一种全新的使用体验。 首先&#xff0c;酷开系统壁纸模式的视觉效果十分出色。它采用了高清的图像质量和细腻的色彩渲染&a…

jvm堆概述

《java虚拟机规范》中对java堆的描述是&#xff1a;所有的对象实例以及数组都应当在运行时分配在堆上。 一个JVM实例只存在一个堆内存(就是new 出来一个对象)&#xff0c;java内存管理的核心区域 java堆区在jvm启动的时候就被创建&#xff0c;空间大小确定。是jvm管理的最大一…

智能革新:2024年AI辅助研发的挑战、机遇与未来展望

引言 在进入2024年的门槛时&#xff0c;我们站在了一个科技飞速发展的新纪元&#xff0c;其中&#xff0c;人工智能&#xff08;AI&#xff09;的持续进步和应用扩展无疑是推动这一变革的强大动力。AI辅助研发&#xff0c;作为将人工智能技术应用于科研和产品开发过程的一种模…

把握机遇:2024年游戏行业春招提前批全攻略

当前&#xff0c;国内游戏行业正处于高速发展期&#xff0c;各大游戏公司对应届毕业生的人才需求十分旺盛。这一趋势不仅为即将步入职场的学生们提供了广阔的就业前景&#xff0c;也为游戏产业的创新和多元化发展注入了新鲜血液。 在这样的大环境下&#xff0c;2024年春季提前批…

基于qt的图书管理系统----05其他优化

参考b站&#xff1a;视频连接 源码github&#xff1a;github 目录 1 优化借阅记录显示2 时间显示为年月日3 注册接口 1 优化借阅记录显示 现在只能显示部分信息&#xff0c;把接的书名和人的信息全部显示 在sql语句里替换为这一句即可实现查询相关联的所有信息 QString str…

html--彩虹爱心

文章目录 js内容cssreset.min.cssstyle.css html内容 js内容 const colors ["#e03776","#8f3e98","#4687bf","#3bab6f","#f9c25e","#f47274"]; const SVG_NS http://www.w3.org/2000/svg; const SVG_XLINK &q…

训练验证码之ddddocr一个图文视频教学

目录 一、推荐文章视频一、ddddocr环境配置二、字符集验证码训练三、ocr_api_server服务搭建 一、推荐文章视频 文章原文来自这里&#xff1a;训练验证码-4、ddddocr训练字符验证码 &#xff0c; 原文文章末尾有视频介绍更多内容见训练验证码合集 一、ddddocr环境配置 1.打开…

【漏洞复现】Salia PLCC cPH2 远程命令执行漏洞(CVE-2023-46359)

0x01 漏洞概述 Salia PLCC cPH2 v1.87.0 及更早版本中存在一个操作系统命令注入漏洞&#xff0c;该漏洞可能允许未经身份验证的远程攻击者通过传递给连接检查功能的特制参数在系统上执行任意命令。 0x02 测绘语句 fofa&#xff1a;"Salia PLCC" 0x03 漏洞复现 ​…

300分钟吃透分布式缓存-24讲:Redis崩溃后,如何进行数据恢复的?

Redis 持久化是一个将内存数据转储到磁盘的过程。Redis 目前支持 RDB、AOF&#xff0c;以及混合存储三种模式。 RDB Redis 的 RDB 持久化是以快照的方式将内存数据存储到磁盘。在需要进行 RDB 持久化时&#xff0c;Redis 会将内存中的所有数据以二进制的格式落地&#xff0c;每…

Swift 入门学习:集合(Collection)类型趣谈-上

概览 集合的概念在任何编程语言中都占有重要的位置&#xff0c;正所谓&#xff1a;“古来聚散地&#xff0c;宿昔长荆棘&#xff1b;游人聚散中&#xff0c;一片湖光里”。把那一片片、一瓣瓣、一粒粒“可耐”的小精灵全部收拢、吸纳的井然有序、条条有理&#xff0c;怎能不让…

【C++专栏】C++入门 | 函数重载、引用、内联函数

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;C专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ C入门 | 函数重载、引用、内联函数 文章编号&#xff1a;C入门 / 02 文…

【机器学习】在Python中进行K-Means聚类和层次聚类

Python中聚类算法API的使用指南 聚类分析是数据分析中一种常见的无监督学习方法&#xff0c;通过将相似的对象分组在一起&#xff0c;我们能够识别出数据集中的自然分群。本文将介绍如何使用Python中的聚类算法接口&#xff0c;KMeans和层次聚类方法。 K-Means 聚类 K-Means…

利用 Redis 和 Lua 实现高效的限流功能

简介 在现代系统中&#xff0c;限流是一种重要的机制&#xff0c;用于控制服务端的流量并保护系统免受恶意攻击或请求泛滥的影响。本文将介绍如何利用 Redis 和 Lua 结合实现高效的限流功能。 一、什么是限流 限流指的是对系统中的请求进行控制和调节&#xff0c;确保系统在…

动手学深度学习PyTorch版

基本的数据操作 import torch # 创建一个行向量&#xff0c;默认为从0开始的12个整数 # n维数组也称为张量 x torch.arange(12) x # 张量的形状 x.shape# 张量的大小,张量所有元素的个数 x.numel()#修改张量的形状 x x.reshape(3,4)#生成形状为3*4的两个向量&#xff0c;向…

离散数学——(4)

目录 1.主析取范式 2.大项 3.主合区范式 4.范式的求法 真值表法 5.推理理论 直接证法 1.主析取范式 2.大项 3.主合区范式 4.范式的求法 真值表法 5.推理理论 直接证法

验证码安全

目录 验证码识别&复用&调用&找回密码重定向&状态值 res 修改-找回密码修改返回状态值判定验证通过 验证码爆破-知道验证码规矩进行无次数限制爆破 短信轰炸原理 验证码识别&复用&调用&找回密码重定向&状态值 res 修改-找回密码修改返回状态…
最新文章