【 C++ 】C/C++内存管理

news2024/11/15 11:47:47

前言:

😘我的主页:OMGmyhair-CSDN博客

目录

一、C/C++内存分布

二、C语言中动态内存管理方式:malloc/calloc/realloc/free

malloc:

calloc:

realloc:

free: 

三、C++内存管理方式

1.用new/delete操作内置类型

2.用new/delete操作自定义类型

3.operator new和operator delete函数


一、C/C++内存分布

二、C语言中动态内存管理方式:malloc/calloc/realloc/free

malloc:

在c语言中,我们可以使用malloc进行动态申请内存:

int main()
{
	int* p = (int*)malloc(sizeof(int) * 2);
	return 0;
}

从上面我们可以看到我们申请了8个字节大小的空间。malloc函数的返回值是void*类型的指针,指向已经开辟好的空间的首地址。我们可以通过强转,转为自己需要的类型。我们用malloc申请到的空间,里面没有初始化,值是不确定的。

如果申请空间失败,将会返回空指针。


calloc:

int main()
{
	//malloc:
	int* p = (int*)malloc(sizeof(int) * 2);

	//calloc:
	int* pc = (int*)calloc(2, sizeof(int));

	return 0;
}

在上面代码中,我们用calloc申请了2个大小为int的空间。calloc函数的返回值是void*类型的指针,指向已经开辟好的空间的首地址。它与malloc的区别在于,calloc申请到的空间每个比特位都会初始化为0。


realloc:

int main()
{
	//malloc:
	int* p = (int*)malloc(sizeof(int) * 2);

	//calloc:
	int* pc = (int*)calloc(2, sizeof(int));

	//realloc:
	pc = (int*)realloc(pc, sizeof(int) * 4);

	return 0;
}

realloc可以用来重新申请空间,第一个参数是原空间的地址,第二个参数是新内存块的大小

如果在原空间的地址上不能往后继续申请内存(后面的位置被占用了),那么realloc会重新开辟一块新内存空间,将原空间上的数据搬到新内存空间,并且对原空间进行释放。新内存空间对于原空间如果更大了,那么多出来的那部分是没有进行初始化的。

如果第一个参数是空指针,那么此时realloc的作用类似于malloc。

当realloc申请空间失败,会返回空指针,但是原空间依旧有效且数据还在。我们用realloc申请巨大的空间来模拟申请空间失败的情况:

int main()
{
	int* p = (int*)calloc(2, sizeof(int));
	cout<<"realloc前p的地址:" << p << endl;
	int* pp = p;
	p = (int*)realloc(p, sizeof(int) * 1024*1024*1024*1024);
	cout <<"realloc后p的地址:" << p << endl;
	cout << "原空间的第一个数:" << pp[0] << endl;
	return 0;
}

结果:

所以我们在使用realloc的时候,要小心申请失败的情况:

int main()
{
	int* p = (int*)calloc(2, sizeof(int));
	int* ppr = (int*)realloc(p, sizeof(int) * 24);
	if (ppr != NULL)
	{
		p = ppr;
	}
	return 0;
}

free: 

如果ptr没有指向由calloc、realloc、malloc开辟的空间,那么产生的结果是不确定的。

但如果ptr是空指针,不会做任何事情。

需要注意的是,free后不会改变ptr的值,ptr依旧指向那片空间,只是此时你在去使用这块空间是非法的。


此处插播一条知识点,为什么在32位环境下指针大小是4个字节?而在64位环境下指针大小是8个字节?

举个例子,当行李箱上假设有3位密码,每一位的范围是从0~9,我们要用多少位数可以表示全部的密码呢?答案是10^{3}

一个字节8个比特位。

首先,指针装的是地址,在32位系统下,内存地址空间大小是2^{32}。当我们表示地址时,每一位的范围是0~1,一共32个比特位,地址也就需要用32个比特位去表示,也就是4个字节。

那么64位环境下就更好理解了。在64位系统下,内存地址空间大小是2^{64}。当我们表示地址时,每一位的范围是0~1,一共64个比特位,地址也就需要用64个比特位去表示,也就是8个字节。


三、C++内存管理方式

1.用new/delete操作内置类型

int main()
{
	//动态申请一个int大小的内存空间
	int* p1 = new int;

	//动态申请一个int大小的内存空间,并且初始化为1
	int* p2 = new int(1);

	//动态申请一个3个int大小的内存空间
	int* p3 = new int[3];

	//动态申请一个3个int大小的内存空间,并且初始化为0、1、2
	int* p4 = new int[3] {0, 1, 2};

	delete p1;
	delete p2;
	delete[]p3;
	delete[]p4;
	return 0;
}

当我们申请/释放单个元素的空间时,使用new/delete。当我们申请或者释放连续的空间时,使用new[]或者delete[]。注意要搭配起来使用,如果不搭配使用的后果在文章的后面会讲到。


2.用new/delete操作自定义类型

在这里,new/delete和malloc/free的区别就明显体现出来了。

class A
{
public:
	A(int a1 = 1, int a2 = 2)
		:_a1(a1)
		,_a2(a2)
	{
		cout << "A(int a1 = 1, int a2 = 2)" << endl;
	}

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

private:
	int _a1;
	int _a2;
};

int main()
{
	cout << "malloc:" << endl;
	A* ma = (A*)malloc(sizeof(A));
	cout << "-------------------------------------" << endl;

	cout << endl << "new:" << endl;
	A* na = new A;
	cout << "-------------------------------------" << endl;

	cout << endl << "free:" << endl;
	free(ma);
	cout << "-------------------------------------" << endl;

	cout << endl << "delete:" << endl;
	delete na;
	cout << "-------------------------------------" << endl;
	return 0;
}

看看运行结果:

可以看到对比malloc,new会去调用自定义类型的构造函数。而对比free,delete会去调用自定义类型的析构函数。

对于内置类型而言new/delete和malloc/free是几乎一样的。


3.operator new和operator delete函数

new delete 是用户进行 动态内存申请和释放的操作符 operator new operator delete
系统提供的 全局函数 new 在底层调用 operator new 全局函数来申请空间, delete 在底层通过
operator delete 全局函数来释放空间。
对于自定义类型,new会进行 开空间+调用构造函数,而这里开空间用的是operator new来申请空间。
为什么不用malloc来申请空间呢?
如果malloc申请失败会返回空指针,而C++希望有另外一套执行来应对申请失败的情况:如果malloc申请空间成功就直接返回,否则执行用户的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。
这里的operator new就相当于升级版的malloc。
对于自定义类型,delete会先析构再使用operator delete进行释放空间,而operator delete的底层是_free_dbg。
这里的_free_dbg是什么呢?
首先free其实是宏函数,它的底层是_free_dbg。
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

总结一下:


4.new和delete的实现原理

(1)内置类型

如果是对内置类型进行申请空间,new/delete和malloc/free基本类似。不同的地方在于,new/delete申请和释放的是单个元素空间,new[]/delete[]申请和释放的是连续的空间,而且new在申请空间失败时会抛出异常而malloc是返回空指针。

int main()
{
	//抛异常
	try
	{
		while (1)
		{
			int* p = new int[1024 * 1024];
			int* p1 = new int[1024 * 1024];
		}
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

(2)自定义类型

new和delete对于自定义类型的原理,前面已经描述过,这里就不再赘述。

(2)-1 new [N]的原理

1.调用operator new[]函数,这里的operator new[]其实就是调用operator new实现对N个对象空间申请

2.在申请的空间上进行N次构造函数

(2)-2 delete[]的原理

1.在将要释放的对象空间上调用N个析构函数,实现对N个对象的资源清理。

2.调用operator delete[]释放空间,实际上就是调用operator delete来释放空间。


5.new/delete和new[]/delete[]错误搭配以及使用free进行释放new

(1)内置类型

int main()
{
	int* p = new int[3];
	delete p;
	return 0;
}

这个代码有什么危险吗?会产生内存泄漏吗?

答案是什么都不会发生。

因为new[]归根结底还是malloc,delete归根结底也还是free。所以在这个场景下你甚至还能用free来释放空间(不推荐)。

(2)自定义类型

1.场景1

如果我们用free去释放new的空间,编译器不会报。我们也会发现free不会调用析构函数。如果我们在析构函数中有资源的释放,可能会造成内存泄漏。

2.场景2
class A
{
public:
	A(int a1 = 1, int a2 = 2)
		:_a1(a1)
		,_a2(a2)
	{
		cout << "A(int a1 = 1, int a2 = 2)" << endl;
	}

private:
	int _a1;
	int _a2;
};

int main()
{
	A* p1 = new A[3];
	delete p1;
	return 0;
}

正常通过。(注意和场景3的对比)

3.场景3
class B
{
public:
	B(int b1 = 1, int b2 = 2)
		:_b1(b1)
		, _b2(b2)
	{
		cout << "B(int b1 = 1, int b2 = 2)" << endl;
	}

	~B()
	{
		cout << "~B()" << endl;
	}
private:
	int _b1;
	int _b2;
};

int main()
{
	B* p1 = new B[3];
	delete p1;
	return 0;
}

查看运行结果:

对比与场景2,同样是用delete对new[]申请的资源进行释放,为什么B类运行时就产生了崩溃呢?

delete[]与delete的区别

我们通过比较可以发现,B跟A相比多了析构函数。当用new[]申请多个B类对象的空间时,new[]其实会在有析构函数的类前面多开出4个字节的空间来存储对象个数。因为你有析构函数,编译器怕你析构函数中有释放资源的操作,所以会记下对象的个数来多次调用析构函数避免内存泄漏。如果你没有析构函数,编译器认为没有释放资源,也就懒得给你另开空间记个数了:

A类和B类大小一样,都是8个字节,2个int成员变量。我们来看看new[]对A类开辟了多少空间,对B类又开辟了多少空间。

我们都知道不能对申请的空间只释放一部分。当B类有析构时,使用delete进行释放就是只对申请的空间释放一部分,导致了程序崩溃。所以我们在使用new/delete和new[]/delete[]时,一定要搭配使用,以免造成错误。


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

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

使用格式如下:

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

在这里,我们用operator new或者malloc去开辟空间,这里不调用构造,用operator new不用像malloc一样检查返回值,因为operator new直接抛异常。

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a = 0)" << 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);//释放空间

	A* p2 = (A*)malloc(sizeof(A));//开辟空间
	new(p2)A();//调用构造函数
	p2->~A();//调用析构函数
	free(p2);//释放空间

	return 0;
}

你可能会觉得这不是脱裤子放屁吗?为什么不直接使用new一键开辟空间+调用构造函数呢?

这里要引入一个概念,池化技术——提高性能,像类似内存池、线程池、连接池都属于池化技术。而我们这里涉及的就是内存池。

我们可以将内存池比作一个受到管理的大湖,周围的人都需要到这里来打水。而山上有座寺庙,频繁用水经常排队。因此干脆专门给寺庙开了一个小水洼,这个小水洼供寺庙专用,当小水洼中的水不够了就可以去大湖要。而这里的内存池就是小水洼,堆就是大湖。

使用定位new表达式也是为了提高效率。


7.malloc/freenew/delete的区别

共同点:

都是从堆上申请空间,并且需要用户手动释放。

不同点:

1. malloc free 是函数, new delete 是操作符
2. malloc 申请的空间不会初始化, new 可以初始化
3. malloc 申请空间时,需要手动计算空间大小并传递, new 只需在其后跟上空间的类型即可,
如果是多个对象, [] 中指定对象个数即可
4. malloc 的返回值为 void*, 在使用时必须强转, new 不需要,因为 new 后跟的是空间的类型
5. malloc 申请空间失败时,返回的是 NULL ,因此使用时必须判空, new 不需要,但是 new
要捕获异常
6. 申请自定义类型对象时, malloc/free 只会开辟空间,不会调用构造函数与析构函数,而 new
在申请空间后会调用构造函数完成对象的初始化, delete 在释放空间前会调用析构函数完成
空间中资源的清理释放



如果这篇文章有帮助到你,请留下您珍贵的点赞、收藏+评论,这对于我将是莫大的鼓励!学海无涯,共勉!😘😊😗💕💕😗😊😘




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

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

相关文章

用Mapmost聚类图分析世界

聚类地图是一种数据可视化工具&#xff0c;能够帮助用户在地图上直观地显示大量地理数据点。当数据点过多时&#xff0c;单独显示每个点会使地图变得混乱&#xff0c;而聚类地图通过将相近的数据点聚集在一起&#xff0c;减少了视觉复杂性&#xff0c;便于分析和理解。聚类地图…

在Linux上安装中创中间件InforSuiteAS(二进制文件安装)

在Linux上安装中创中间件InforSuiteAS&#xff08;二进制文件安装&#xff09; 前言1、环境准备1.1 支持的操作系统1.2 依赖软件 2、安装步骤2.1 下载并解压安装包2.2 执行安装2.3 修改防火墙设置2.4 启动InforSuiteAS2.5 InforSuiteAS常用命令2.6 验证安装 3、常见问题及解决方…

【Petri网导论学习笔记】Petri网导论入门学习(三)

Petri网导论入门学习&#xff08;三&#xff09; Petri 网导论学习笔记&#xff08;三&#xff09;定义 1.4定义 1.5定义 1.6定义 1.7 Petri 网导论学习笔记&#xff08;三&#xff09; 如需学习转载请注明原作者并附本帖链接&#xff01;&#xff01;&#xff01; 如需学习转载…

Axure设计之全屏与退出全屏交互实现

在Axure RP中&#xff0c;设计全屏与退出全屏的交互功能可以极大地提升用户体验&#xff0c;尤其是在展示产品原型或进行演示时。本文将详细介绍如何在Axure RP中通过结合JavaScript代码实现全屏与退出全屏的交互效果。 ​ Axure原型设计web端交互元件库&#xff1a;https://…

网络安全产品认证证书大全(持续更新...)

文章目录 一、引言二、《计算机信息系统安全专用产品销售许可证》2.1 背景2.2 法律法规依据2.3 检测机构2.4 检测依据2.5 认证流程2.6 证书样本 三、《网络关键设备和网络安全专用产品安全认证证书》3.1 背景3.2 法律法规依据3.3 检测机构3.4安全认证和安全检测依据标准3.5 认证…

9月→2024年计算机与信息安全国际会议

【9月→郑州、吉隆坡双会场】 Springer-LNICST &#x1f525;&#x1f525;2024年计算机与信息安全国际会议&#xff08;WCCIS 2024&#xff09; 会议时间&#xff1a;2024年9月20-22日 论文收录&#xff1a;EI&#xff0c;Scopus稳定检索 网络安全&#xff0c;访问控制&am…

了解MySQL 高可用架构:主从备份

为了防止数据库的突然挂机&#xff0c;我们需要对数据库进行高可用架构。主从备份是常见的场景&#xff0c;通常情况下都是“一主一从/(多从)”。正常情况下&#xff0c;都是主机进行工作&#xff0c;从机进行备份主机数据&#xff0c;如果主机某天突然意外宕机&#xff0c;从机…

Android 13 固定systemUI的状态栏为黑底白字,不能被系统应用或者三方应用修改

目录 一.背景 二.思路 三.代码流程 1.colos.xml自定义颜色 2.设置状态栏的背景颜色 3.对View进行操作 ①.对Clock(状态栏左侧的数字时钟)进行操作 ②.对电池(BatteryMeterView)进行操作 4.锁屏状态栏 5.patch汇总 一.背景 客户需求将状态栏固定成黑底白字,并且不能让系…

红外小目标检测:基于深度学习

目录 ​编辑 1.红外成像技术的优势 2.红外小目标检测的基本原理 常用方法 1. 背景抑制法 2. 基于滤波的方法 3. 基于模型的方法 4. 基于深度学习的方法 5. 多传感器融合方法 3.代码实战 案例背景 数据准备 模型选择 代码实现 讲解 4.应用场景 5.未来发展趋势 …

NISP 一级 | 4.2 操作系统的安全威胁

关注这个证书的其他相关笔记&#xff1a;NISP 一级 —— 考证笔记合集-CSDN博客 0x01&#xff1a;漏洞和漏洞扫描 对计算机系统安全威胁最大的就是系统本身的漏洞&#xff0c;只有存在漏洞&#xff0c;黑客才有机会入侵我们的计算机系统。具统计证明&#xff0c;99% 的黑客攻击…

基于spring实现博客项目的删除和更新(五)

8. 实现用户退出 前端直接清除掉token即可. 实现客⼾端代码 <注销>链接已经提前添加了onclick事件 &#xff0c;在common.js中完善logout⽅法 function logout(){localStorage.removeItem("user_token");location.href "blog_login.html"; } 点击…

Adobe 将推出人工智能视频模型 Firefly 视频模型: 最长 5 秒,支持视频编辑

最近&#xff0c;Adobe 发布了一款全新的创意工具–Adobe Firefly 视频模型。 这一创新工具标志着 Adobe 在现有 Firefly 生成式人工智能图像模型的基础上&#xff0c;大胆涉足人工智能生成视频领域。 Adobe 表示&#xff0c;该模型是经过道德训练的&#xff0c;使用的数据都是…

数字孪生之-3D可视化

定义&#xff1a; 广义&#xff1a;一切现实物体的虚拟化表达&#xff0c;都可以算是广义的数字孪生行业&#xff1a;数字孪生体应该是与现实物体一对一映射、实时数据连接、有数据模型和对应的数据的 个人理解数字孪生的实现还是基于数据驱动&#xff0c;加上上帝视角&#xf…

微软九月补丁星期二发现了 79 个漏洞

微软将在2024 年 9 月补丁星期二修复 79 个漏洞。 微软有证据表明&#xff0c;发布的四个漏洞被野外利用和/或公开披露&#xff1b;所有四个漏洞均已在CISA KEV上列出。微软还在修补四个关键的远程代码执行 (RCE) 漏洞。 不同寻常的是&#xff0c;微软本月尚未修补任何浏览器…

Leetcode面试经典150题-141.环形链表

题目比较简单&#xff0c;重点是理解思想 解法都在代码里&#xff0c;不懂就留言或者私信 /*** Definition for singly-linked list.* class ListNode {* int val;* ListNode next;* ListNode(int x) {* val x;* next null;* }* }*/ public…

Transformer学习(2):自注意力机制

回顾 注意力机制 自注意力机制 自注意力机制中同样包含QKV&#xff0c;但它们是同源(Q≈K≈V)&#xff0c;也就是来自相同的输入数据X&#xff0c;X可以分为 ( x 1 , x 2 , . . , x n ) (x_1,x_2,..,x_n) (x1​,x2​,..,xn​)。 而通过输入嵌入层(input embedding)&#xff0c…

环境搭建1

预编译.h 这里面包含一些常用的头文件 #pragma once #include<SDKDDKVer.h> #include<stdio.h> #include<tchar.h> #include<bitset> #include<conio.h> #include"atlstr.h" #include<atlimage.h> #include<iostream> #…

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(2) (*****生成数据结构类的方式特别有趣****)

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(1)-CSDN博客 本节内容 实现目标 通过已经得到的Excel表格…

算法-图论(建图,拓扑排序)

文章目录 建图的三种方式邻接矩阵邻接表链式前向星 拓扑排序拓扑排序基础原理介绍拓扑排序步骤解析拓扑排序模板leetcode-课程表 建图的三种方式 我们建图的三种方式分别是邻接矩阵, 邻接矩阵, 链式前向星 邻接矩阵 假设我们的点的个数为N个, 我们就把他们的下标依次标为1, …

Java邮件:如何配置以实现自动化邮件通知?

Java邮件发送性能优化策略&#xff1f;怎么实现Java 发邮件功能&#xff1f; Java邮件API提供了一个强大且灵活的框架&#xff0c;使得开发者能够轻松地集成邮件发送功能到他们的应用程序中。AokSend将详细介绍如何配置Java邮件&#xff0c;以实现自动化邮件通知。 Java邮件&…