【C++】-- 内存管理new和delete详解_c++ new delete-程序员宅基地

技术标签: C++  c++  开发语言  

目录

一、C/C++ 内存分布

二、C语言动态内存管理方式

三、C++使用new和delete 的原因

四、C++动态内存管理方式

         1.new和delete

2.operator new和operator delete

五、内存泄漏


一、C/C++ 内存分布

C/C++内存被分为6个区域:

(1)内核空间存放内核代码和环境变量

(2) 栈(也叫堆栈)存放非静态局部变量/函数参数/返回值等等,栈是向下增长的。

(3)内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。

(4)堆用于程序运行时动态内存分配,堆是向上增长的。

(5)数据段存储全局数据和静态数据。

(6)代码段存放可执行的代码/只读常量。

如下图所示:

二、C语言动态内存管理方式

C语言动态内存管理malloc、calloc、realloc、free的详细介绍请参考文章C语言-动态内存管理详解

三、C++使用new和delete 的原因

new和delete是C++向内存申请空间和释放空间的操作符, C++为什么要使用new和delete?

1.C语言使用malloc、calloc、realloc、free管理的不便之处在于:

(1)手动申请内存需要手动计算字节数的大小

(2)对返回值类型void *要进行强转,否则无法解引用

(3)内存是否申请成功需要对返回值进行判空

(4)需要include头文件<stdlib.h>

2.new和delete的使用对自定义类型会进行处理:

(1)new会调用构造函数对类对象进行初始化

(2)delete会调用析构函数进行资源清理

四、C++动态内存管理方式

  1.new和delete

 (1)new/delete和malloc/free对内置类型的操作没有区别:

①申请和释放单个空间,使用new和delete

②申请和释放连续空间,使用new[ ]和delete[ ]

#include<stdlib.h>
int main()
{
	//操作内置类型

	//1.申请单个int对象
	int* p1 = (int*)malloc(sizeof(int));
	free(p1);

	int* p2 = new int;//int* p2 = new int(10); 申请单个int对象并将其初始化为10
	delete p2;
	
	//2.申请10个元素的int数组
	int* p3 = (int*)malloc(sizeof(int) * 10);
	free(p3);

	int* p4 = new int[10];
	delete[] p4;//自定义类型数组delete要带上[],否则不匹配,程序会意外终止
	
	return 0;
}

C++11支持用{}对数组初始化:

int* p5 = new int[5]{1,2,3,4,5};
delete[] p5;

 (2)new/delete和malloc/free对自定义类型的操作有区别:

①malloc/free对自定义类型的操作只会开空间/释放空间

②new操作自定义类型会开空间+初始化,delete操作自定义类型会调用析构函数清理+释放空间

#include<iostream>
#include<stdlib.h>
using namespace std;

struct ListNode
{
	ListNode* _next;
	ListNode* _prev;
	int _val;

	ListNode(int val = 0)
		: _next(nullptr)
		, _prev(nullptr)
		, _val(val)
	{
		cout << "this.val =" << val << endl;
	}

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

int main()
{
	struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
	struct ListNode* n2 = new ListNode;

	free(n1);
	delete n2;

	return 0;
}

F10监视,走完28行执行了new操作,发现n1 malloc的3个成员变量是随机值,而n2 new的3个成员变量都被初始化了,所以new不仅会开空间,还会调用自定义类型的构造函数进行初始化: 

 走完31行,执行了delete操作,发现打印了析构函数的内容,调用了析构函数:

 因此,C++不仅开空间还初始化,不仅释放空间还清理资源。

总结:

(1)C++如果为内置类型申请空间,malloc和new没有区别

(2) C++如果为自定义类型申请空间,区别很大:

          ①new和是开空间+初始化,delete是析构清理+释放空间

          ②malloc仅仅是开空间,free仅仅是释放空间

建议:C++中,无论是内置类型,还是自定义类型的申请释放,尽量使用new和delete

如何在堆上申请4GB的空间?在32位机器上执行下面代码,程序会崩溃,因为2^32=1024*1024*1024*4,32位机器一共才4G,根据C++内存分布,内核空间占1G,剩余的5个区域一共占3G,因此32位平台是申请不到4GB内存空间的。

void* p1 = malloc(1024 * 1024 * 1024 * 4);
cout << p1 << endl;

但是,在VS上将项目-项目属性-配置管理器-选择x64平台:

 这时64位机器共2^64,大概160亿GB左右,远远大于4G,顺利申请到了4G空间,但这4G其实也是虚拟的:

2.operator new和operator delete

定义:operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间,它们不是new和delete的重载。

与malloc区别:operator new、operator delete的用法和malloc、free的用法是一样的,功能都是在堆上申请释放空间,但是失败了的处理方式不一样,malloc失败返回NULL,operator new失败以后抛异常。

使用new为自定义类型ListNode申请空间并初始化:new会调用operator new函数和构造函数,operator new会调用malloc和失败抛异常机制,因此new和operator new申请失败都会抛异常:

从上图可以看出,new并不直接调用 malloc,而是调用了operator new,因为operator new被封装了,malloc申请失败直接报返回值,但operator new申请失败会抛异常。

delete先析构把资源清理掉,再调用operator delete释放栈空间本身。

在32位系统中申请0x7fffffff字节大小的空间,operator new函数申请空间失败抛异常:

void f()
{
	void* p3 = malloc(0x7fffffff);
	if (p3 == NULL)
	{
		cout << "malloc fail " << endl;
	}

	void* p4 = operator new(0x7fffffff);
    cout << "申请空间ing" << endl;

    char* p5 = new char('C');
	char* p6 = new char[2];

    ListNode* p7 = new ListNode[5]{1,2,3,4,5};
}

int main()
{
	
	try
	{
		f();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

 申请空间失败:没有打印申请空间ing,说明抛异常会直接跳到catch的位置,就不会执行cout << "申请空间ing" << endl;

F10-调试-反汇编,对于内置类型,发现new调的是operator new,new 类型[ ] 其实调的就是operator new[ ]:

 对于自定义类型,会调operator new 和ListNode构造函数

malloc/free和new/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在释放空间前会调用析构函数完成空间中资源的清理。

五、内存泄漏

定义:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不 是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会 导致响应越来越慢,最终卡死。

假如申请了堆内存,但是忘记释放,会造成内存泄漏,但只是指针丢失,而不是内存丢失:

int main()
{
	int *p1 = (int *)malloc(sizeof(int));

	return 0;
}

为什么内存不会丢?

因为在堆上申请了一块空间后,拿到了指向这块空间的指针,现在就可以访问这块空间了。但是访问之后由于疏忽或者程序设计错误,没有了这块指针,或者不用了这块指针,但并不知道指针没释放。而内存始终存在,把空间分配使用者的意思是,这块空间的使用权交给了使用者,free就是把这块内存空间还给了操作系统,现在系统就可以把这块空间再重新分配给别的使用者,因此如果不归还内存,内存会越来越少,直到最后无法正常使用。

普通程序就算内存泄漏,问题也不大,因为程序运行的是进程,每个进程都有自己的运行空间,它们一起映射物理地址,进程只要正常结束,就算有内存泄漏,最后也会被释放掉。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/gx714433461/article/details/124393974

智能推荐

2021年美赛C题目简述(中英文)_2021美赛c题-程序员宅基地

文章浏览阅读1w次,点赞4次,收藏15次。2021年美赛C题目2021年美国大学生C题目为大数据类型题目,常用统计类的相关算法模型,推荐SAS,其次Stata,最后SPSS。2021年美赛C题目(中文):2021年美赛C题目(英文):相关算法以及代码实现会更新,敬请关注!最后祝愿各位同学争取佳绩!..._2021美赛c题

2023年全国职业院校技能大赛高职组大数据应用开发赛题第07套_1、根据hive的dwd库中相关表或mysql中ds_db01中相关表,计算出与用户customer-程序员宅基地

文章浏览阅读736次,点赞22次,收藏26次。环境说明:子任务一:Hadoop 完全分布式安装配置本任务需要使用root用户完成相关配置,安装Hadoop需要配置前置环境。命令中要求使用绝对路径,具体要求如下:1、从宿主机/opt目录下将文件hadoop-3.1.3.tar.gz、jdk-8u212-linux-x64.tar.gz复制到容器Master中的/opt/software路径中(若路径不存在,则需新建),将Master节点JDK安装包解压到/opt/module路径中(若路径不存在,则需新建),将JDK解压命令复制并粘贴至客户端桌面_1、根据hive的dwd库中相关表或mysql中ds_db01中相关表,计算出与用户customer_id

stm32f429跑linux,使用Buildroot编译STM32F429 linux内核,文件系统成功-程序员宅基地

文章浏览阅读760次。## Booting kernel from Legacy Image at c0007fb4 ...Image Name: linux-4.12_STM32F429Image Type: ARM Linux Kernel Image (uncompressed)Data Size: 2994400 Bytes =2.9 MBLoad Address: c0008000Entry..._stm32f429 linux

C语言----输入scanf和输出printf详解_scanf输入什么就一定会以printf的形式输出什么吗?-程序员宅基地

文章浏览阅读2.5k次,点赞4次,收藏22次。C语言编程中,输入输出是基本操作,printf和scanf并不是C语言中的唯一的输入输出选择,对于输入有scanf()、getchar()、getche()、getch()、gets();对于输出有printf()、puts()、putchar(),他们各有自己的使用场景,本篇文章主要介绍常用的scanf和printf。_scanf输入什么就一定会以printf的形式输出什么吗?

python图书推荐系统 爬虫技术 大数据 协同过滤推荐算法 计算机 毕业设计(附源码)-程序员宅基地

文章浏览阅读1.1k次,点赞30次,收藏25次。python图书推荐系统 爬虫技术 大数据 协同过滤推荐算法 计算机 毕业设计(附源码)

opengl 实现Skin Mesh(骨骼动画)- GPU_gpuskin-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏21次。上一篇博客实现了cpu skin(蒙皮)的骨骼动画,接下来我们要实现 gpu skin(蒙皮) 的骨骼动画首先我们需要知道什么是蒙皮,在上一博客实现的骨骼动画中,哪一步是蒙皮 。在模型文件中可以读到的数据是顶点和骨骼的对应关系,从动画文件中可以读到每一帧骨骼的数据,而根据每一帧骨骼的数据计算出顶点位置的过程就叫做蒙皮,也就是对应上一篇博客代码中的ComputeVertPos函数。另外还要知道为..._gpuskin

随便推点

图形验证码+短信验证码【Java应用实例】_图形验证码下面如何写短信验证码-程序员宅基地

文章浏览阅读2.6w次,点赞19次,收藏60次。一、图形验证码的实现1.1 简介常在网上晃悠的人,对下面这张图都不会陌生。特别是在注册新账号、确认交易时,它们都会频繁出现,要求我们输入正确的验证码,那这些看上去跟我们要做的事情完全无关的验证码到底有何作用呢?其实,验证码的校验即是一个直接的图灵测试,简单的逻辑是:服务提供者向用户方提出一个较为复杂的问题,能正确回答的即是人类,回答不出的即是机器或者程序代码。这个概念基于这样一个重..._图形验证码下面如何写短信验证码

【UiPath2022+C#】UiPath Switch_uipath switch控件-程序员宅基地

文章浏览阅读1.2w次。本文主要介绍了UiPath的Switch语句实现方式。这大概是最详细的UiPath使用中文文档,环境:UiPath Studio Community 2022.4.3/Windows/C#,网上的文档和教程基本都是VB的,本文基于C#学习UiPath。_uipath switch控件

浅析 Node 进程与线程_nodejs pipe占用主线程吗?-程序员宅基地

文章浏览阅读267次。进程与线程是操作系统中两个重要的角色,它们维系着不同程序的执行流程,通过系统内核的调度,完成多任务执行。今天我们从 Node.js(以下简称 Node)的角度来一起学习相关知识,通过本文读者将了解 Node 进程与线程的特点、代码层面的使用以及它们之间的通信。概念首先,我们还是回顾一下相关的定义:进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配..._nodejs pipe占用主线程吗?

(零)OpenStack ( Train版 ) 搭建环境_openstack train-程序员宅基地

文章浏览阅读849次。大多数 OpenStack 环境中包含认证、镜像、计算和至少一个网络服务,还有仪表盘,仪表盘要求至少要有镜像服务,计算服务和网络服务。配置每个节点必须用有管理员权限的帐号。可以用 root用户或 sudo 工具来执行这些命令。由于 Openstack 服务数量以及虚拟机数量,为了获得最好的性能,我们推荐你的环境满足或者超过最小的硬件需求。如果在增加了更多的服务或者虚拟机后性能下降,请考虑为你的环境增加硬件资源。为了避免混乱和为 OpenStack 提供更多资源,推荐最小化安装 Linux 系统。_openstack train

go 语言常量_go 常量-程序员宅基地

文章浏览阅读877次。go 语言常量_go 常量

MemSQL:号称世界上最快的内存-关系型数据库 兼容MySQL但快30倍_memsql 写入性能-程序员宅基地

文章浏览阅读2.9k次。MemSQL是一款内存数据库,它通过将数据存在内存中,将SQL语句预编译为C++而获得极速的执行效率。MemSQL宣称这是世界上最快的分布式关系型数据库,兼容MySQL但快30倍,能实现每秒150万次事务。 MemSQL由前Facebook工程师Eric Frenkiel和微软SQL Server高级工程师Nikita Shamgunov(CTO)联合创办,MemSQL的高性能数据库还参照了Fac_memsql 写入性能

推荐文章

热门文章

相关标签