《C++内存管理》

news2025/1/13 15:56:08

本文主要介绍C++内存管理的知识,主要包括new和delete,其实很简单,类比我们的C语言的内存管理malloc/free,就是在堆上申请内存的

小知识点:
C++构造对象的顺序:先构造全局再构造局部静态对象最后构造普通对象
析构顺序则相反

文章目录

  • C++内存管理练习题
  • 一、C语言中内存管理方式
  • 二、 C++中动态内存管理
    • 1、 概念
    • 2、new/delete操作内置类型
    • 3、new/delete操作自定义类型
  • 三、 operator new与operator delete函数
    • 1、 new的机制
    • 2、 delete的机制
  • 四、malloc/free和new/delete的区别
  • 五、 定位new(主要用来对内存池的空间初始化)
  • 六、内存泄漏
    • 1、概念
    • 2、内存泄漏的分类
      • 第一种:堆内存泄漏(Heap leak)
      • 第二种:系统资源泄漏
      • 如何避免内存泄漏


C++内存管理练习题

我们先来看一个题:

int globalVar = 1;
static int staticGlobalVar = 1; 
void Test()
{
	static int staticVar = 1; 
	int localVar = 1;
	int num1[10] = { 1, 2, 3, 4 }; 
	char char2[] = "abcd";
	const char* pChar3 = "abcd";
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); 
	free(ptr1);
	free(ptr3); 
}

选项:

  1. A.栈
  2. B.堆
  3. C.数据段(静态区)
  4. D.代码段(常量区

globalVar在哪里?C
staticGlobalVar在哪里?C
staticVar在哪里?C
localVar在哪里?A
num1 在哪里?A
char2在哪里?A
*char2在哪里?A
pChar3在哪里?A
*pChar3在哪里?D
ptr1在哪里?A
*ptr1在哪里?B

解释:

  1. globalVar属于全局变量,所以存放在静态区。
  2. staticGlobalVar,属于静态的全局变量,静态成员存放在静态区
  3. staticVar静态局部变量,静态成员存放在静态区
  4. localVar,是一个局部变量,存放在栈区
  5. num1,是一个数组名,表示首元素的地址,所以也是一个局部变量存放到栈区中
  6. char2,是一个数组名,表示首元素的地址,所以也是一个局部变量存放到栈区中。
  7. *char2,解引用,表示数组首元素的数据我们char2是一个在栈上的数组,将我们的常量字符拷贝的数组上,所以我们*char存放到栈上。(易错)
  8. pChar3,是一个常量指针,在这个函数中,所以属于栈区。
  9. ·*pChar3·,对于这个常数组解引用,表示这个常量字符串,既然是常量,就存放在常量区。(易错)
  10. ptr1 是一个地址,存放在函数上,所以也是一个局部变量存放到栈区中。(易错)
  11. *ptr1我们动态开辟的空间,存放在堆上。(易错)

图解:
在这里插入图片描述在这里插入图片描述

总结:

  1. 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的,地址越来越小
  2. 堆是程序员自己malloc/new出来的,用于程序运行时动态内存分配,堆是可以上增长的。地址越来越大
  3. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
    创建共享共享内存,做进程间通信。
  4. 数据段(静态区)–存储全局数据和静态数据。
  5. 代码段–可执行的代码/只读常量(常量字符串)

一、C语言中内存管理方式

我们先回忆一下malloc calloc,realloc
malloc:开辟空间,但是不初始化。
calloc:开辟空间,但是初始化为0.
realloc:扩容,但是分为原地扩容,和异地扩容

这个看一下原地扩容和异地扩容即可:
在这里插入图片描述

情况1(原地扩容):当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2(异地扩容):当前内存后面空间大小不够,重新寻找内存,单独开辟一块全新的空间,空间大小满足调整大小。将原先空间的数据先拷贝到当前空间,再释放掉原先的空间返回新开辟空间的起始地址。

具体代码如下:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(40); //申请40个字节的空间
	int i = 0;
	if (p == NULL)
	{
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(ptr + i));
	}
	
	//增加空间
	//p = (int*)realloc(p, 1000);//这样不可以,如果扩容失败就GG了
	int* ptr = (int*)realloc(p, 80);  //增加80个字节
	if (ptr != NULL)
	{
		p = ptr;  //如果扩容成功,再将扩容后的返回值赋给p
		ptr = NULL;  //此时ptr没用了,将ptr置为NULL,防止其成为野指针
	}
	//使用
	for (i = 10; i < 20; i++)
	{
		*(p + i) = i;
	}
	//打印
	for (i = 10; i < 20; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放
	free(p);
	p = NULL;
}



二、 C++中动态内存管理

1、 概念

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

2、new/delete操作内置类型

int main()
{
	int* p1 = new int;	//这个不会初始化
	delete p1;
	
	int* p2 = new int(10);//这个初始化,申请一个,初始化为10
	delete p2;
	
	int* p3 = new int[10];//创建10个数据的数组
	delete[]p3;
	
	int* p4 = new int[10]{ 1,2,3,4 };//10个数据的数组初始化,将前4个初始化为特定的,其他为0
	delete[]p4;
	
	return 0;
}

对比一下C语言的malloc/free是不是轻松多了

int main()
{
	//malloc/free
	int* p = (int*)malloc(sizeof(int));
	if (p == nullptr)
	{
		perror("malloc fail");
		exit(-1);
	}
	free(p);
	p = nullptr;

	//new/delete
	int* pp = new int;
	delete pp;
	pp = nullptr;

	return 0;
}

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用
new[]和delete[],注意:匹配起来使用。

3、new/delete操作自定义类型

new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
//1:自定义类型
	// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
	A* p1 = (A*)malloc(sizeof(A));
	A* p2 = new A(1);
	free(p1);
	delete p2;

	A* p3 = (A*)malloc(sizeof(A)*10);
	A* p4 = new A[10];
	free(p5);
	delete[] p6;

//2:内置类型
	// 内置类型是几乎是一样的
	int* p5 = (int*)malloc(sizeof(int)); // C
	int* p6 = new int;
	free(p5);
	delete p6;

return 0;
}

注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与
free不会。


三、 operator new与operator delete函数

注意:

  1. operator new operator delete是系统提供的 全局函数。并不是运算符重载。
  2. new底层调用operator new全局函数来申请空间.
  3. delete底层通过 operator delete 全局函数来释放空间。

operator new参数类型:跟malloc一样

void* operator new (size_t size) throw (bad_alloc);

operator new和malloc的区别:

operator new失败了会抛异常
malloc失败了会返回空指针

int main()
{
	//失败了抛异常
	int* p1 = (int*)operator new(sizeof(int));

	//失败返回空指针
	int* p2 = (int*)malloc(sizeof(int));
	if (p2 == nullptr)
	{
		perror("malloc fail");
	}

}

图示:
在这里插入图片描述


1、 new的机制

new的两个作用:

  1. operator new申请空间
  2. 调用构造函数

因为面向对象的语言,失败了都喜欢用抛异常而不是空指针。
所以在申请空间的时候用operator new将malloc封装,然后失败了就会抛异常

2、 delete的机制

delete的两个作用:

  1. 先调用析构函数
  2. 再用 operator delete释放空间

在释放空间的时候用operator new将free封装。

为什们 delete和 free不要混着用?

我们如果用 new开辟空间,用 free释放空间,我们就可能不调用析构函数。直接是释放空间。(因为释放空间的时候用 operator new将 free封装)。

结论:

new/malloc系列,有底层实现机制有关联交叉。
可能有问题,可能没问题,必须匹配使用。


四、malloc/free和new/delete的区别

共同点:

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

特点6:new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。

我们写一个栈:


class Stack
{
public:
	//构造函数
	Stack(int capacity = 4)
		:_capacity(capacity)
	{
		cout << "Stack()" << endl;
		_a = new int[capacity];
		_top = 0;
	}

	//析构函数
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_top = _capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack* pst = new Stack; //new一个栈

	delete pst;
	pst = nullptr;
	return 0;
}

代码剖析:
在这里插入图片描述
在这里插入图片描述

当我们实用delete释放pst指向的空间前,会先调用析构函数完成空间内部资源的清理
在这里插入图片描述

注意:如果我们不用delete,直接使用free就会导致内存泄漏,因为free直接将Stack空间清理了,但是空间内部还有_a数组开辟的空间
在这里插入图片描述


五、 定位new(主要用来对内存池的空间初始化)

假设我们先用malloc开辟空间了,但是我们没有初始化。
这时我们去初始化,有没有什么办法呢?
就要用我们的定位new

定位 new的作用:

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

定位 new的使用格式:

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

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
	}
private:
	int _a;
};
int main()
{
	A aa;
	A* p1 = (A*)malloc(sizeof(A));
	if (p1 == nullptr)
	{
		perror("malloc fail");
	}
	
	//定义new
	//p1找到要初始化的位置
	//A(1)构造函数。初始化内容
	new(p1)A(1);
	
	//释放空间
	p1->~A();
	free(p1);
}

使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。


六、内存泄漏

1、概念

什么是内存泄漏,内存泄漏的危害?

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

2、内存泄漏的分类

第一种:堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,
用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

第二种:系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统
资源的浪费,严重可导致系统效能减少,系统执行不稳定。

如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。注意:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵

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

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

相关文章

一些解决方案

文件异步下载方案 1 set QueryBussessType manually different type --> different resolving code、wherecondition 2. frontend request with the type 3. get excelHeader --> groovyUtil load from db 4. getData from db with pagination 5. saveData in an excel 6…

【测试面试】你要的宝典,软件接口测试面试题大全(总结)--附答案

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、接口测试主要目…

将war包发布到容器中的tomcat

文章目录 将war包直接发布到容器中使用数据卷将war包持久化到docker的宿主机(CentOS7) 将war包直接发布到容器中 1、将windows中的文件通过xftp程序传到centOS7中 2、创建容器&#xff0c;通过docker中的命令将web.jar复制到tomcat容器中 # 查看docker中的镜像 ~]$ docker imag…

0.8秒捕捉,速度超乎想象,小米和WPS用户太激动,office用户已用

只需0.8秒就能捕捉 距离4月18日小米13ultra发布会时间还剩一天。这几天雷军的微博已经把小米13ultra的亮点已经做了很多铺垫宣传。 除了系统和硬件之外&#xff0c;就是这次小米13ultra最大的“杀手锏”的就是“徕卡相机”。连宣传文案都改成了&#xff1a;一个伟大的瞬间&…

【UE】玩家位置存档

在上一篇博客中&#xff08;【UE】将存档的值显示在控件蓝图上&#xff09;我们介绍了如何将存档的值显示在控件蓝图上&#xff0c;本篇博客要介绍的是如何将玩家位置进行存档。 效果 可以看到重新进入游戏时&#xff0c;角色在存档点出现&#xff0c;而不是玩家出生点 步骤 …

XMU 算法分析与设计第三次上机题解

文章目录 一、BFS试炼之微博转发二、DFS试炼之不同路径数三、并查集试炼之合并集合并查集的介绍 四、堆排序堆排序的介绍 五、厦大GPA&#xff08;分组背包&#xff09;分组背包介绍 六、消防安全指挥问题&#xff08;最短路Floyd&#xff09;七、铺设光纤问题(最小生成树Prim)…

干货满满~如何解决跨域!!

1. 为什么会存在跨域 首先要知道&#xff0c;在浏览器/app中使用异步请求(ajax)发送到服务器时&#xff0c;会出现跨域问题。若是服务与服务之间通信是没有跨域这一说的 2. 浏览器为什么要设置跨域的存在&#xff1f; 为了防止恶意网页可以获取其他网站的本地数据&#xff0…

4.23、TCP状态转换(为什么四次挥手)

4.23、TCP状态转换 1.TCP状态转换图2.为什么需要四次挥手&#xff0c;状态转换 1.TCP状态转换图 2.为什么需要四次挥手&#xff0c;状态转换 2MSL&#xff08;Maximum Segment Lifetime&#xff09; 主动断开连接的一方, 最后进入一个 TIME_WAIT状态, 这个状态会持续: 2msl ms…

实例化构造方法static统统都学会

文章目录 前言一、实例化是什么&#xff1f;二、构造方法1.概念2.特性3.. 如果用户没有显式定义&#xff0c;编译器会生成一份默认的构造方法&#xff0c;生成的默认构造方法一定是无参的 四.static1.static修饰成员变量2.static修饰成员方法3.static成员变量初始化 总结 前言 …

AR实战-基于Krpano的多场景融合及热点自定义

背景 在之前的博客中&#xff0c;曾经介绍了关于Krpano的相关知识&#xff0c;原文&#xff1a;全景自动切片技术-krpano初识。简单讲解了基于krpano1.19-pr13下单张全景照片的处理与展示。随着实景中国在各地的落地生根&#xff0c;三维园区、三维景区、三维乡村等等需求的集中…

ERP系统给企业管理带来哪些改变?

企业资源计划&#xff08;ERP&#xff09;系统是一种综合性的管理工具&#xff0c;它可以集成和管理企业内部所有的业务流程和信息。自上世纪90年代以来&#xff0c;ERP系统已成为许多企业的重要工具&#xff0c;为企业管理带来了巨大的变革。 第一&#xff0c;ERP系统可以将企…

ArrayList与顺序表

目录 ​编辑 一、线性表 二、顺序表 1、接口的实现 &#xff08;1&#xff09;打印顺序表 &#xff08;2&#xff09;新增元素 &#xff08;3&#xff09;判定是否包含某个元素 &#xff08;4&#xff09;查找某个元素对应的位置下标 &#xff08;5&#xff09;获取 …

基于QTableView中的MVD代理添加总结

目录 1、Qt中MVD说明 1.1 View 1.2 Delegate 1.3 Model/View的基本原理 2、代码是现实示例 2.1 设置样式文件 2.2 set base attribute 2.3 设置model 2.4 设置表头 2.5 设置数据 2.6 添加代理控件 2.6.1 添加 QSpinBox 代理 2.6.2 添加 QComboBox 代理 2.6.…

【JS】vis.js使用之vis-timeline使用攻略,vis-timeline在vue3中实现时间轴、甘特图

vis.js使用之vis-timeline使用攻略&#xff0c;vis-timeline实现时间轴、甘特图 1、vis-timeline简介2、安装插件及依赖3、简单示例4、疑难问题集合1. 中文zh-cn本地化2. 关于自定义class样式无法被渲染3. 关于双向数据绑定 vis.js是一个基于浏览器的可视化库&#xff0c;它提供…

深度探索vector

vector是什么 &#xff1f; vector就是一个可以自动扩充的array。 源码解析 vector主要是通过三个指针来维护的&#xff0c;分别是起点&#xff0c;当前终点&#xff0c;以及当前最大空间 sizeof(vector对象) 3 * 指针大小 vector每当遇到空间不同的情况&#xff0c;都会…

Windows逆向安全(一)之基础知识(十三)

Switch语句 先前讲了分支结构的if else形式&#xff0c;除此之外还有一种分支结构&#xff1a;switch 此次就来以反汇编的角度研究switch语句&#xff0c;并与if else进行比较 Switch语句的使用 有关Switch语句在vc6.0中生成的反汇编可分为4种情况&#xff0c;这4种情况的区…

不用科学上网,免费的GPT-4 IDE工具Cursor保姆级使用教程

1、Cursor 编辑器 可以直接官方网站下载&#xff1a;https://www.cursor.so/ &#xff08;这里以Mac为例&#xff09; 这是一款与OpenAI合作并且基于GPT4的新一代辅助编程神器&#xff0c;它支持多种文件类型&#xff0c;支持格式化文本、多种主题、多语言语法高亮、快捷键设…

react-7 组件库 Ant Design Mobile(移动端)

1.安装组件库 npm install --save antd-mobile 常用组件 tabbar 底部导航 Swiper 轮播图&#xff08;走马灯&#xff09; NavBar&#xff08;顶部返回累&#xff09; 配合 Dialog&#xff0c;Toast InfiniteScroll 无限滚动&#xff08;实现下拉刷新&#xff09; Skeleto…

ROS学习第九节——服务通信

1.基本介绍 服务通信较之于话题通信更简单些&#xff0c;理论模型如下图所示&#xff0c;该模型中涉及到三个角色: ROS master(管理者)Server(服务端)Client(客户端) ROS Master 负责保管 Server 和 Client 注册的信息&#xff0c;并匹配话题相同的 Server 与 Client &#…

远程控制电脑的软件哪个比较好用

有多种软件选项可用于远程控制计算机&#xff0c;最适合您的软件选项取决于您的具体需要和要求。 以下是一些最流行的远程控制软件选项及其功能和优势&#xff1a; TeamViewer TeamViewer 是使用最广泛的远程控制软件选项之一。 它具有用户友好的界面&#xff0c;并提供文件传…