C/C++:动态内存管理

news2024/11/21 1:34:38

目录

一. C/C++内存分布

二. C/C++动态内存管理

2.1 C语言动态内存管理

2.2 C++动态内存管理

2.2.1 new/delete操作符

2.2.2 operator new与operator delete函数

2.3 new/delete的实现原理

2.4 定位new(placement - new)

2.5 new/delete和malloc/free的区别

三. 内存泄漏

3.1 什么是内存泄漏

3.2 内存泄漏的危害


一. C/C++内存分布

对于任何一个C/C++程序,其程序内存可以划分为下面几个区域:

  • 内核空间:用户代码不能读写
  • 栈区:存储非静态的局部变量、函数参数和返回值等。
  • 内存映射段:用于装载一个共享的动态内存库,用户可使用系统接口创建共享内存,做进程间通信。
  • 堆区:如果程序中需要用到较大的内存区域,则用户可以手动在堆区申请内存空间。
  • 数据段:用于存储全局数据和静态数据。
  • 代码段:用于存储可执行代码和只读常量。

图1.1以一段具体的程序为例,表述了每块内存区域存储的内容。

图1.1  C/C++程序内存区域分布示意图

二. C/C++动态内存管理

2.1 C语言动态内存管理

涉及四个主要函数,分别为malloc、calloc、realloc、free

  • malloc:动态申请一块内存空间,不进行初始化,函数原型为void* malloc(size_t size),在使用malloc函数的返回值是要进行强制类型转换。
  • calloc:动态申请内存空间,并将申请的空间的内容初始化为0。函数原型为:void* calloc(size_t num, size_t size),num表示申请内存空间的块数,size表示每块空间的大小。
  • realloc:调整一块已经申请的内存空间的大小,函数原型为void* realloc(void* ptr, size_t size),其中ptr指向要调整大小的内存空间、size表示调整后的内存空间大小。
  • free:释放动态申请的内存空间。

2.2 C++动态内存管理

2.2.1 new/delete操作符

在C++中,可以使用new来申请堆区内存空间,采用delete释放堆区内存空间,new的使用语法为:

  • 申请单块内存空间不初始化:数据类型* ptr = new 数据类型
  • 申请数组空间不初始化:数据类型* ptr = new 数据类型[数据量]
  • 申请单块内存空间并初始化:数据类型* ptr = new 数据类型(初始化值)
  • 申请数组空间并初始化:数据类型* ptr = new 数据类型{初始化值1, 初始化值2, ...... }

delete的使用语法为:

  • 释放单个内存空间:delete 指向动态开辟的内存区域的指针
  • 释放数组空间:delete[] 指向动态开辟的内存区域的指针 -- 其中[]就表示数组
int main()
{
	int* ptr1 = new int;  //申请单个int型空间
	int* ptr2 = new int(5);  //申请单个int型空间并初始化为5

	int* ptr3 = new int[3];  //申请3个int型空间
	int* ptr4 = new int[3]{ 1,2,3 };  //申请3个int型空间并初始化为1、2、3

	delete ptr1;
	delete ptr2;   //释放单个内存空间

	delete[] ptr3;
	delete[] ptr4;  //释放数组内存空间

	return 0;
}

当使用new/delete申请和释放内置数据类型的空间时,其与malloc/free没有除语法以外的太大区别。但是,对于自定义类型(类)空间,使用new申请空间会调用类的构造函数,使用delete释放空间会调用类的析构函数。如果使用malloc/free申请和释放内存空间,则不会调用类的构造函数和析构函数。

int main()
{
	//动态开辟类A的空间

	//使用new动态申请自定义类型的空间时,会调用自定义类型的构造函数
	//malloc-free自定义类型内存空间不会调用其构造函数和析构函数
	A* p1 = new A;
	A* p2 = new A[3];

	//delete自定义类型的内存空间是,会调用其析构函数
	delete p1;
	delete[] p2;  //释放动态开辟的内存空间

	A* p3 = new A(10, 20);  //申请类A型的内存空间并初始化

	//申请3个类A型的内存空间并初始化
	//注意:(10,20)会被解读为逗号表达式20,相当于只有构造函数的第一个参数给了值,第二个参数使用默认值
	//(30,40)、(50,60)同理
	A* p4 = new A[3]{ (10,20), (30, 40), (50, 60) }; 

	//给构造函数传两个参数初始化要使用花括号{}而非小括号()
	A* p5 = new A[3]{ {10,20}, {30, 40}, {50, 60} };

	delete p3;
	delete[] p4;
	delete[] p5;

	return 0;
}

2.2.2 operator new与operator delete函数

对于一门语言,其处理异常的方式可能是下面两种:

  • 面向过程的语言(C) :返回值 + 错误码errno
  • 面向对象的语言(C++、JAVA等):抛异常 -- 可使用try catch来捕获异常

如C语言中的malloc函数,如果malloc函数申请动态内存失败,那么函数就返回空指针NULL,而operator new是C++提供的动态内存申请函数,其底层就是通过调用malloc函数来实现的。而为了满足面向对象的语言出错抛异常的要求,如果动态内存开辟失败,operator new会把malloc的返回值报错方式转换为抛出异常。

operator new的使用语法:数据类型* ptr = (数据类型*)operator new(空间大小)

operator delete与operator new配对使用,释放动态申请的内存空间。

注意:operator new动态申请自定义类型内存空间不调用构造函数。

int main()
{
	int* ptr1 = (int*)operator new(3 * sizeof(int));  //开辟3个int型空间
	A* ptr2 = (A*)operator new(sizeof(A));  //开辟类A型空间

	operator delete(ptr1);
	operator delete(ptr2);
	
	return 0;
}

如果采用operator new动态申请的空间过大,那么程序就会抛出异常。演示代码2.1采用operator new函数申请一块大小为2G的内存空间(申请空间过大导致失败),采用try - catch捕获异常,运行程序输出bad allocation。

演示代码2.1:

int main()
{
	char* ptr = nullptr;
	try
	{
		ptr = (char*)operator new(1024u * 1024u * 1024u * 2u);
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}
图2.1 演示代码2.1的运行结果

如果程序抛出异常,那么其执行流会马上跳转到catch的位置处,哪怕是跨函数,也会跳转,并且,catch后面的语句会照常执行,知道程序结束或程序崩溃。演示代码2.2在函数func中申请了一块2G的内存空间,在申请完空间后输出"operator new successful",但是,由于期望申请的空间过大,导致申请失败,因此,程序不会输出"operator new successful",而是会直接跳转执行catch中的程序,输出bad allocation。

演示代码2.2:

char* func()
{
	char* ptr = (char*)operator new(1024u * 1024u * 1024u * 2u);
	cout << "operator new successful" << endl;
	return ptr;
}

int main()
{
	char* ptr = nullptr;
	try
	{
		ptr = func();
	}
	catch(const exception& e)
	{
		cout << "operator new fail" << endl;
		cout << e.what() << endl;
	}

	delete ptr;
	return 0;
}
图2.2 演示代码2.2运行结果

2.3 new/delete的实现原理

new/delete的底层是通过调用operator new和operator delete来实现的。

  • new的实现原理:(1)调用operator new申请内存空间  (2)调用自定义类型的构造函数
  • delete的实现原理:(1)调用自定义类型的析构函数  (2)调用operator delete释放内存空间
  • new[]的实现原理:(1)调用operator new[]申请空间  (2)调用自定义类型的构造函数
  • delete[]的实现原理:(1)调用自定义类型的析构函数  (2)调用operator delete[]释放空间
图2.3 new(左)和delete(右)的实现原理示意图

2.4 定位new(placement - new)

使用operator new申请动态内存空间,不会调用自定义类型的构造函数。但是有时候我们希望在operator new函数申请的空间上调用构造函数,可构造函数却不支持直接显式调用,这是就需要用到定位new来实现。

定位new使用语法:new(指向动态开辟的内存空间的指针)类名(传给构造函数的参数)

虽然构造函数不能显示调用,但是析构函数可以显示调用。

演示代码2.3:

class A
{
public:
	A(int a1 = 10, int a2 = 20, double a3 = 20.2)
		: _a1(a1)
		, _a2(a2)
		, _a3(a3)
	{
		std::cout << "A(int a1 = 10, int a2 = 20, double a3 = 20.2)" << std::endl;
	}

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

private:
	int _a1;
	int _a2;
	double _a3;
};

int main()
{
	A* pa1 = nullptr;
	A* pa2 = nullptr;
	try
	{
		pa1 = (A*)operator new(sizeof(A)); 
		pa2 = (A*)operator new(sizeof(A));
	}
	catch (const std::exception& e)
	{
		std::cout << e.what() << std::endl;
	}

	//类的构造函数不能显示调用
	new(pa1)A;
	new(pa2)A(20, 30, 40.4);

	//析构函数可以显示调用
	(*pa1).~A();
	(*pa2).~A();

	operator delete(pa1);

	return 0;
}
图2.4 演示代码2.3的运行结果

2.5 new/delete和malloc/free的区别

  • 用法区别:
  1. malloc需要手动计算申请空间的大小,new只需要在数据类型后面的[]内输入所要申请类型空间的个数即可。
  2. malloc的返回值类型为void*,需要强制类型转换,而new在使用时会显示说明申请空间的类型,无需强制类型转换。
  • 底层区别:
  1. malloc/free是函数,new/delete是操作符。
  2. malloc申请空间失败返回NULL,需要判空,而new申请空间失败会抛异常,需要使用catch捕获异常。
  3. 申请自定义类型空间时,malloc/free只会申请空间,不会调用构造函数和析构函数,new/delete不但会申请和释放空间,还会调用默认构造函数完成初始化以及调用析构函数进行对象资源的清理。

三. 内存泄漏

如果申请了动态内存空间却不手动释放,就会造成内存泄漏。

3.1 什么是内存泄漏

动态申请内存空间,不使用了,但却没有释放,就存在内存泄漏,使可用内存越来越少。

3.2 内存泄漏的危害

  • 对于正常结束的进程,进程结束时泄漏掉的内存会自动还给系统,不会有太大危害。
  • 对于非正常结束的进程,如僵尸进程,以及需要长期运行的程序,如服务器程序,出现内存泄漏的危害就很大,系统会变得越来越慢,甚至卡死宕机。

所以,动态申请的内存空间一定要记得释放!释放动态内存使用的函数(操作符)一定要与申请内存时用的函数(操作符)匹配:malloc--free、new -- delete、new[] -- delete[]。

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

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

相关文章

代码随想录算法训练营day49 | 动态规划 123.买卖股票的最佳时机III 188.买卖股票的最佳时机IV

day49123.买卖股票的最佳时机III1.确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组188.买卖股票的最佳时机IV1.确定dp数组以及下标的含义2.确定递推公式4.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组123.买卖股票的最佳时机III …

Zookeeper3.5.7版本——选举机制(非第一次启动)

目录一、ZooKeeper集群中哪些情况会进入Leader选举二、当一台机器进入Leader选举流程时&#xff0c;当前集群的两种状态2.1、集群中本来就已经存在一个Leader2.2、集群中确实不存在Leader三、Zookeeper中的一些概念了解3.1、SID3.2、ZXID3.3、Epoch一、ZooKeeper集群中哪些情况…

谈“对象“

面向对象一切皆对象&#xff0c;和java一样&#xff0c;各编程语言一样的思想规范类名首字母大写&#xff0c;和java一样创建的规范python3创建类的时候&#xff0c;可以不带括号&#xff0c;也可以带&#xff0c;也可以显示继承object&#xff0c;如果带个()空括号&#xff0c…

【网络】序列化和反序列化

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【网络】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站 文章…

WSL2使用Nvidia-Docker实现深度学习环境自由部署

1. Win11 显卡驱动的安装 注意&#xff1a;WSL2中是不需要且不能安装任何显卡驱动的&#xff0c;它的显卡驱动完全依赖于 Win11 中的显卡驱动&#xff0c;因此我们只需要安装你显卡对应的 Win11 版本显卡驱动版本&#xff08;必须是 Win11 版本的驱动&#xff09;&#xff0c;…

Three.js高级应用--利用Three.js+WebGL实现fbx和obj格式模型的自定义加载

通过对webgl和three.js的不断学习与实践&#xff0c;在三维应用场景建设过程中&#xff0c;利用Three.js与webgl配合可以实现大部分三维场景的应用需求&#xff0c;这一篇主要讲述如何利用Three.js加载已有的模型&#xff0c;支持的三维模型格式有.fbx和.obj&#xff0c;同时.o…

English Learning - L2 第1次小组纠音 [ɑː] [ɔː] [uː] 2023.2.25 周六

English Learning - L2 第1次小组纠音 [ɑː] [ɔː] [uː] 2023.2.25 周六共性问题分析大后元音 [ɑː]大后元音 [ɔː]后元音 [uː]我的发音问题后元音 [uː]大后元音 [ɑː] 和 [ɔː]纠音过程第一次第二次第三次共性问题分析 大后元音 [ɑː] 嘴唇过于松散&#xff0c;没…

SpringMVC文件上传、下载、国际化配置

Java知识点总结&#xff1a;想看的可以从这里进入 目录3.6、文件上传、下载3.6.1、文件上传3.6.2、文件下载3.7、国际化配置3.6、文件上传、下载 3.6.1、文件上传 form 表单想要具有文件上传功能&#xff0c;其必须满足以下 3 个条件。 form 表单的 method 属性必须设置为 p…

Spring基础知识(Spring注解开发大全)

原本xml文件写法 文件头 文件信息 配置Bean 初步修改的xml文件写法 文件头 文件信息 <context:component-scan base-package"要扫描的包"/>注解开发Bean 第一步&#xff1a;写config文件 Configuration//代表xml文件的文件头 ComponentScan(“要扫描的包”…

大型JAVA版云HIS医院管理系统源码 Saas应用+前后端分离+B/S架构

SaaS运维平台多集团多医院入驻强大的电子病历完整文档 有源码&#xff0c;有演示&#xff01; 云HIS系统技术栈&#xff1a; 1、前端框架&#xff1a;AngularNginx 2、后台框架&#xff1a;JavaSpring&#xff0c;SpringBoot&#xff0c;SpringMVC&#xff0c;SpringSecurity&…

【2022.1.3】手脱压缩壳练习(含练习exe)

【2022.1.3】手脱压缩壳练习&#xff08;含练习exe&#xff09; 文章目录【2022.1.3】手脱压缩壳练习&#xff08;含练习exe&#xff09;0、简介1、单步跟踪法&#xff08;#&#xff09;方法介绍&#xff08;0&#xff09;练习exe下载&#xff08;1&#xff09;、查看源程序&am…

精确率与召回率,ROC曲线与PR曲线

精确率与召回率&#xff0c;ROC曲线与PR曲线 在机器学习的算法评估中&#xff0c;尤其是分类算法评估中&#xff0c;我们经常听到精确率(precision)与召回率(recall)&#xff0c;ROC曲线与PR曲线这些概念&#xff0c;那这些概念到底有什么用处呢&#xff1f; 首先&#xff0c…

Linux系统GPIO应用编程

目录应用层如何操控GPIOGPIO 应用编程之输出GPIO 应用编程之输入GPIO 应用编程之中断在开发板上测试GPIO 输出测试GPIO 输入测试GPIO 中断测试本章介绍应用层如何控制GPIO&#xff0c;譬如控制GPIO 输出高电平、或输出低电平。应用层如何操控GPIO 与LED 设备一样&#xff0c;G…

【办公类05-03】Python批量修改文件名前面的序号(已有的序号错了,需要改成正确的号码)

背景需求下载教程&#xff0c;手动输入编号&#xff0c;有一个编号错误&#xff0c;导致后面所有编号都错了。30实际是29&#xff0c;以此类推怎样才能快速修改编号数字&#xff1f;前期考虑到可能要改编号&#xff0c;所以在每个编号后面加“ ”&#xff08;空格&#xff09;&…

python版协同过滤算法图书管理系统

基于协同过滤算法的图书管理系统 一、简介&#xff08;v信&#xff1a;1257309054&#xff09; ​ 本系统基于推荐算法给用户实现精准推荐图书。 ​ 根据用户对物品或者信息的偏好&#xff0c;发现物品或者内容本身的相关性&#xff0c;或者是发现用户的相关性&#xff0c;然…

Typora上传文档图片链接失效的问题+PicGo布置图床在Github

文章目录typora图片链接失效原因PicGO开源图床布置先配置Github2.1先创建新仓库、用于存放图片2.2生成一个token&#xff0c;用picGo访问github3.下载picGo,并进行配置3.1 配置v4.1typora图片链接失效原因 因为你是保存在本地的&#xff0c;因此图片是不能访问&#xff0c;可以…

laravel 邮件发送

配置 Laravel 的邮件服务可以通过 config/mail.php 配置文件进行配置。 邮件中的每一项都在配置文件中有单独的配置项&#xff0c;甚至是独有的「传输方式」&#xff0c;允许你的应用使用不同的邮件服务发送邮件 mailers > [smtp > [transport > smtp,host > env(M…

【超级猜图案例上半部分的实现 Objective-C语言】

一、超级猜图这么一个案例: 1.实现之后的效果是这样的: 1)中间有一个图片,点一下,能放大,背景变半透明的黑色: 2)再点一下图片,或者点周围黑色的阴影,图片回归原状, 3)右边有一个“大图”按钮,点一下,实现跟点图片一样的效果, 4)左边有一个“提示”按钮,点…

【Java学习笔记】4.Java 对象和类

前言 本章介绍Java的对象和类。 Java 对象和类 Java作为一种面向对象语言。支持以下基本概念&#xff1a; 多态继承封装抽象类对象实例方法重载 本节我们重点研究对象和类的概念。 对象&#xff1a;对象是类的一个实例&#xff08;对象不是找个女朋友&#xff09;&#x…

为什么人们宁可用Lombok,也不把成员设为public?

目录专栏导读一、从零了解JavaBean1、基本概念2、JavaBean的特征3、JavaBean的优点二、定义最简单的JavaBean三、思考一个问题&#xff0c;为何属性是private&#xff0c;然后用get/set方法&#xff1f;四、下面系统的分析以下&#xff0c;why?五、不和谐的声音&#xff0c;禁…