C++——内存管理(new/delete使用详解)

C++内存管理

本章思维导图:
在这里插入图片描述注:本章思维导图对应的xmind文件和.png文件已同步导入至资源

1. C/C++内存区域的划分

在C/C++中,内存区域主要划分为:内核区域、栈区、内存映射段、堆区、数据段、代码段等区域,如图:

在这里插入图片描述

本篇我们主要讨论栈区、堆区、数据段和代码段这四个区域。

栈区:主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。存放在栈区的变量的生命周期就是所在函数的作用域

堆区:主要存放的是malloc、realloc、calloc、new等动态开辟函数开辟的空间。存放在堆区的变量的生命周期是整个进程

数据段:主要存放的是我们在函数外定义的全局数据,或者static静态数据。存放在数据段的变量的生周期是整个进程

代码段:主要存放的是const只读常量即字面量

下面,我们通过一个具体的例子来弄清楚各类数据到底存放在计算机的那一块空间:

int globalVar = 1;
static int staticGlobalVar = 1;

void Test()

{
	static int staticVar = 1;
	const int localVar = 1;
	
	int num1[10] = {1, 2, 3, 4};
	char char2[] = "abcd";
	char* pChar3 = "abcd";

	int* ptr1 = (int*)malloc(sizeof (int)*4);

	free (ptr1);
	free (ptr3);
}

Question:

/*  
选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)
   globalVar在哪里?____   staticGlobalVar在哪里?____   staticVar在哪里?____   const localVar在哪里?____
   num1 在哪里?____
   
   char2在哪里?____   *char2在哪里?___
   pChar3在哪里?____      *pChar3在哪里?____
   ptr1在哪里?____        *ptr1在哪里?____
*/
  • globalVar定义在全局(函数外),是一个全局变量,因此存放在数据段
  • staticGlobalVar也是一个定义在全局的静态变量,因此也存放在数据段(静态区)
    • 需要注意:尽管globalVarstaticGlobalVar都存放在数据段中,但是它们之间也有链接属性的区别。globaVar可以多文件共同使用,而staticGlobalVar只能在本文件使用。
  • staticVar尽管定义在函数内,但是由static修饰,是一个静态变量,因此也存放在数据段
  • const localVar定义在函数内,是一个局部变量,因此存放在栈区
    • 注:千万不要认为localVarconst修饰他就存放在代码段(常量区),const只是修饰局部变量localVar,表示它的值不能被修改。
  • num1是静态开辟的数组,所以存放在栈区
  • char2也是个静态开辟的字符串数组,因此也存放在栈区

  • *char2:这里的char2表示首元素的地址,所以*char2就表示字符a。看到这里,可能有小伙伴就会说*char2存放在常量区,但事实上*char2还是存放在栈区。我们可以从两个方面解释:

    • 常量区的数据不能被修改但是对于*char2,我们可以对其进行修改,也就是普通的堆字符数组进行修改,因此*char2不在常量区,而是在栈区。

    • 实际上,初始化字符数组char2的字符abcd是存放在常量区的字符abcd的拷贝,这份拷贝同样存放在栈区:

      在这里插入图片描述

  • pchar3就是定义在函数内的局部变量,因此存放在栈区

  • pchar3指针指向的就是字符串字面量abcd,而字符串字面量const常量,因此存储在代码段

  • ptr1就是定义在函数内的局部变量,因此存放在栈区

  • ptr1指向的是malloc开辟的空间,因此*ptr1存放在堆区

2. new/delete

在这里插入图片描述

  • C++使用newdelete动态管理内存
  • newdelete是C++内置的两个运算符,不需要显示的包含头文件来使用。

2.1 new/delete和malloc/free的区别

有小伙伴可能会有疑惑:

既然C语言已经可以用malloc、realloc、free等函数来动态管理内存了,为什么C++还要新创建两个运算符newdelete来替代C语言的方法呢?

一个原因是,C++是面向对象的,C++有C语言没有的class类,类的初始化必须调用构造函数,但是构造函数不能显示调用,例如:

class A
{
public:
	A(int a = 1)
		:_a(a)
	{

	}

private:
	int _a;
};

int main()
{
	A* p = (A*)malloc(sizeof(A));
	p->A();

	return 0;
}

//会报错:类型名称“A”不能出现在类成员访问表达式的右侧

可见,C语言并不能解决C++自定义类型初始化的问题,所以C++才要新创建两个运算符newdelete来解决这一问题。

这次,我们换用C++的new运算符来动态开辟内存,并用delete进行空间的释放:

class A
{
public:
	A(int a = 1)
		:_a(a)
	{

	}
    
    ~A()
    {
	}

private:
	int _a;
};

int main()
{
	A* p = new A;
    delete p;

	return 0;
}

让我们进行调试:

在这里插入图片描述

可以看到:

  • new在给自定义类型开辟空间时,会在给对象开辟完空间后继续调用该对象的默认构造完成初始化

  • delete在释放自定义类型对象之前,会先调用对象的析构函数,再释放对象的空间

除了上面最重要的不同之外,malloc/freenew/delete还有许多不同之处:

  • malloc/free是函数,而new/delete是运算符
  • malloc开辟空间时需要用sizeof计算开辟空间的大小,而new只需要在后面加上开辟对象的类型就行
  • malloc的返回值时void*,实际使用时需要强制类型转换;而new不需要,因为开辟时就指定了对象类型
  • malloc失败时,会返回0,因此需要对结果进行判空;而new不需要,但是需要进行异常捕获

2.2 new/delete的使用

如果一次只开辟、释放单个对象,基本格式为:

  • new type;
  • 例如:int*p = new int; delete p;

如果一次开辟、释放多个对象,基本格式为:

  • new type[nums]
  • 例如:int*p = new int[10]; delete[] p

对于自定义类型,newdelete的使用和内置类型基本一致,但是我们可以在new的同时给默认构造传参(如果可以传的话),这样可以一次创建初始值不同的多个同一类型的多个对象:

例如:

class AB
{
public:
	AB(int a = 1, int b = 1)
		: _a(a)
		, _b(b)
	{

	}
private:
	int _a;
	int _b;
};

class C
{
public:
	C(int c = 1)
		:_c(c)
	{

	}
private:
	int _c;
};

int main()
{
	AB* p1 = new AB;	//生成一个对象:_a = 1, _b =  1
	AB* p2 = new AB{ 2,2 };		//生成一个对象: _a = 2, _b =  2
	C* p3 = new C(2);	//生成一个对象:_c = 2

	C* p4 = new C[3];	//生成三个对象:_c都为1
	C* p5 = new C[3]{ 2, 3, 4 };	//生成三个对象:_c分别为2,3,4
	AB* p6 = new AB[3]{ {2,2}, {3,3}, {4,4} };	//生成三个对象: _a, _b分别为(2,2)、(3,3)、(4,4)

	delete p1;
	delete p2;
	delete p3;

	delete[] p4;
	delete[] p5;
	delete[] p6;

	return 0;
}

特别注意

new/deletenew[]/delete[]必须配套使用,不能随意搭配!!!

2.3 new/delete的底层原理

在这里插入图片描述

实际上,执行运算符new开辟空间和delete释放空间时,编译器都会调用operator newoperaotr delete这两个函数

注意:

不要operator newoperaotr delete这两个函数理解为运算符重载

我们可以先来看看operator new的具体实现:

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开空间时实际上用的也是C语言的malloc函数,operator new只不过是对malloc的一层封装。那么为什么要对malloc进行封装呢

malloc失败时返回的是整数0,这不符合C++面向对象的要求,因此C++要对malloc进行封装,使内存开辟失败时可以抛出异常

同样**,实际上operator delete也是用C语言的free函数,来实现对空间的释放**

//等效
int* p1 = (int*)malloc(sizeof(int) * 10);
int* p2 = (int*)operator new(sizeof(int) * 10);

//等效
free(p1);
operator delete(p2);

现在,我们对于C++newdelete对于自定义类型空间的开辟和释放就更加明了了:

  • new首先调用operator new来开辟对象的空间,再调用对象的默认构造进行初始化
  • delete首先调用析构函数释放对象成员变量的资源,再调用operator delete释放该对象的空间

2.4 定位new(仅作了解)

在这里插入图片描述

在之后的学习过程中,我们可能会遇到需要显式调用自定义类型构造函数的情况。但是一般情况下,构造函数不支持显式调用,此时定位new就可以帮我们解决这个问题。

  • 定位nwe可以显示调用构造函数
  • 其基本格式为new (place_address) type或者new (place_address) type(initializer-list)
  • place_address就是是一个指向和一个类相同大小空间的指针
  • type就是类类型
  • initializer-list就是构造函数的参数列表

例如:

class A
{
public:
	A(int a = 1)
		: _a(a)
	{

	}

	~A()
	{

	}
private:
	int _a;
};

//构造函数不能显式调用,但是析构函数可以
int main()
{
	A* p = (A*)malloc(sizeof(A));
	new(p)A(10);

	p->~A();

	return 0;
}![请添加图片描述](https://img-blog.csdnimg.cn/49bacd2462d14cf3a6b58056d5206c4a.gif)


下一篇,我们将对C++的模板和泛型编程展开讲解,感兴趣的小伙伴可以关注此专栏。

请添加图片描述

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

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

相关文章

Linux系统软件安装方式

Linux系统软件安装方式 1. 绿色安装2. yum安装3. rpm安装3.1 rpm常用命令 4. 源码安装4.1 安装依赖包4.2 执行configure脚本4.3 编译、安装4.4 安装4.5 操作nginx4.6 创建服务器 1. 绿色安装 Compressed Archive压缩文档包,如Java软件的压缩文档包,只需…

面试?看完这篇就够了-深入分析从点击应用图标到应用界面展示

作者:GeeJoe 从点击桌面图标到应用界面展示 从桌面点击图标到应用界面第一帧绘制出来,整个流程涉及的过程复杂,为了便于理解,这里将整个流程分为四个阶段:应用进程启动阶段、应用进程初始化阶段、Activity 启动阶段、…

Linux系统中如何开启和配置OpenGauss数据库的远程连接(1)

文章目录 前言1. Linux 安装 openGauss2. Linux 安装cpolar3. 创建openGauss主节点端口号公网地址4. 远程连接openGauss5. 固定连接TCP公网地址6. 固定地址连接测试 前言 openGauss是一款开源关系型数据库管理系统,采用木兰宽松许可证v2发行。openGauss内核深度融合…

基于ISO13209(OTX)实现EOL下线序列

一 OTX是什么? OTX,全称Open Test sequence eXchange format,即开放式测试序列交换格式,国际标准:ISO13209,是专为汽车行业制定的序列开发标准。在车辆诊断、自动化标定和ECU测试等领域有广泛应用。OTX不仅…

使用Python轻松实现科研绘图

当撰写在学术期刊上发表的文章时,图表的布局和风格应符合预定义的格式要求。这样可以确保该出版物的所有文章都具有一致的风格,并且任何包含的图表在打印时都是高质量的。 Python在科学界广泛使用,并提供了创建科学绘图的好方法。然而&#…

初始化后执行kubectl get nodes报错:The connection to the server localhost:8080

K8S初始化后,worker节点加了master节点,在master执行kubectl get nodes 报错,这个原因看是路径的问题导致 [rootk8s-master01 ~]# kubectl get nodes E1114 16:28:52.032089 2254 memcache.go:265] couldnt get current server API group…

使用Docker本地安装部署Drawio绘图工具并实现公网访问

目录 前言 1. 使用Docker本地部署Drawio 2. 安装cpolar内网穿透工具 3. 配置Draw.io公网访问地址 4. 公网远程访问Draw.io 前言 提到流程图,大家第一时间可能会想到Visio,不可否认,VIsio确实是功能强大,但是软件为收费&…

Java封装一个根据指定的字段来获取子集的工具类

工具类 ZhLambdaUtils SuppressWarnings("all") public class ZhLambdaUtils {/*** METHOD_NAME*/private static final String METHOD_NAME "writeReplace";/*** 获取到lambda参数的方法名称** param <T> parameter* param function functi…

【LeetCode】挑战100天 Day11(热题+面试经典150题)

【LeetCode】挑战100天 Day11&#xff08;热题面试经典150题&#xff09; 一、LeetCode介绍二、LeetCode 热题 HOT 100-132.1 题目2.2 题解 三、面试经典 150 题-133.1 题目3.2 题解 一、LeetCode介绍 LeetCode是一个在线编程网站&#xff0c;提供各种算法和数据结构的题目&…

Mybatis中limit用法补充

limit a,b a是从第a1条数据开始&#xff0c;b是指读取几条数据 例如&#xff1a;select * from table limit 0,10 这句sql语句是说从表中获取第1条开始的10条记录 前端将page:页码    pageSize:每页多少条    这两个参数&#xff0c;传到后台。    通过这两个参数&am…

实力爆发 | 国民品牌大运新能源亮相广州车展

2023第二十一届广州国际汽车展览会将于11月17日至26日在广州琶洲广交会展馆拉开大幕&#xff0c;本届广州车展以“新科技新生活”为主题&#xff0c;将汇集国内外车企的多款重磅新车及前沿新能源出行技术。 &#xff08;2023广州国际汽车展览会&#xff09; 随着环保意识的提高…

个人类型小程序已支持申请微信认证

小程序申请微信认证 政府、媒体、其他组织类型账号&#xff0c;必须通过微信认证验证主体身份。企业类型账号&#xff0c;可以根据需要确定是否申请微信认证。已认证账号可使用微信支付权限。 个人类型小程序已支持微信认证&#xff08;审核大约需要1-3个工作日&#xff09; …

Unity中Shader的矩阵加减法

文章目录 前言一、什么是矩阵矩阵就是一组数的阵列 二、矩阵的加法三、矩阵的负值四、矩阵的减法五、矩阵的表示 前言 Unity中Shader用到的矩阵加减法&#xff0c;以及矩阵的一些基础常识 一、什么是矩阵 矩阵就是一组数的阵列 1 2 3 4 5 6 二、矩阵的加法 两个矩阵相加就是…

专题解读|Graph Fairness代表性工作介绍

1. 图上的公平性问题 图在现实世界中无处不在&#xff0c;例如知识图谱&#xff0c;社交网络和生物网络。近年来&#xff0c;图神经网络( graph neural networks&#xff0c;GNNs ) 在图结构数据建模方面表现出了强大的能力。一般地&#xff0c;GNNs采用消息传递机制&#xff…

SOME/IP学习笔记3

目录 1.SOMEIP Transformer 1.1 SOME/IP on-wire format 1.2 协议指定 2. SOMEIP TP 2.1 SOME/IP TP Header 3.小结 1.SOMEIP Transformer 根据autosar CP 相关规范&#xff0c;SOME/IP Transformer主要用于将SOME/IP格式的数据序列化&#xff0c;相当于一个转换器。总体…

Cesium 展示——绘制圆的几种方式

文章目录 需求分析需求 总结绘制圆的几种方式 分析 使用圆形几何体(CircleGeometry):var circle = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees

七个合法学习黑客技术的网站,让你从萌新成为大佬

大家好我是若风&#xff0c;一个8年网络安全攻防经验的白帽黑客。 合法的学习网站&#xff0c;以下这些网站&#xff0c;虽说不上全方位的满足你的需求&#xff0c;但是大部分也都能。能带你了解到黑客有关的技术&#xff0c;视频&#xff0c;电子书&#xff0c;实践&#xff0…

Git 安装配置

目录 Linux 平台上安装 Debian/Ubuntu Centos/RedHat 源码安装 Windows 平台上安装 Mac 平台上安装 Git 配置 用户信息 文本编辑器 差异分析工具 查看配置信息 在使用Git前我们需要先安装 Git。Git 目前支持 Linux/Unix、Solaris、Mac和 Windows 平台上运行。 Git …

AR人脸道具SDK,打造极致用户体验

为了满足企业在AR领域的应用需求&#xff0c;美摄科技推出了一款领先的AR人脸道具SDK&#xff0c;旨在帮助企业快速、高效地开发出具有丰富玩法体验的AR应用&#xff0c;从而提升企业的竞争力和市场份额。 一、丰富的AR人脸道具&#xff0c;满足多样化需求 美摄科技AR人脸道具…

【Python图像超分】Real-ESRGAN图像超分模型(超分辨率重建)详细安装和使用教程

1 前言 图像超分是一种图像处理技术&#xff0c;旨在提高图像的分辨率&#xff0c;使其具有更高的清晰度和细节。这一技术通常用于图像重建、图像恢复、图像增强等领域&#xff0c;可以帮助我们更好地理解和利用图像信息。图像超分技术可以通过多种方法实现&#xff0c;包括插值…