广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++的动态内存管理你真的了解吗
  • 243
分享到

C++的动态内存管理你真的了解吗

2024-04-02 19:04:59 243人浏览 薄情痞子
摘要

目录前言用法上对内置类型对自定义类型new/delete底层原理重载类的专属operator new和 operator delete定位newnew/delete与malloc/f

前言

想必大家对C语言的动态内存分配并不陌生,忘了的小伙伴也可以看看我的这篇文章C语言动态内存分配

c语言的动态内存分配由于有些地方用起来比较麻烦同时检查错误的机制不适合c++,因此c++引入new/delete操作符进行内存管理,下面我们来深入探讨c++为什么要引入new/delete

在这里插入图片描述

用法上

对内置类型

new/delete同样是在堆区申请释放空间
new和delete申请释放单个元素的空间,new[]和delete[]申请释放一块连续的空间,new与delete匹配,new[]与delete[]匹配。new后面直接跟类型,不需要强制类型转换。简单来说new/delete用于单个对象,new[]/delete[]用于多个对象。
对内置类型没有什么区别,new操作符和malloc函数一样,不会对空间初始化,唯一的不同在后面的底层原理会介绍

#include <iOStream>
using namespace std;
int main()
{
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = (int*)malloc(sizeof(int) * 10);
	//new后面跟动态申请的类型
	int* p3 = new int;//动态申请一个int型空间
	int* pp3 = new int(1);//动态申请一个int型空间并初始化为1
	//动态申请10个int型空间并初始化
	int* p4 = new int[10]{ 1,2,3 };
	free(p1);
	free(p2);
	delete p3;
	delete pp3;
	delete[] p4;
}

对自定义类型

对自定义类型那就有很大的区别了,new一个自定义对象,在申请空间后还会调用构造函数初始化,delete对象会先调用析构函数清理对象中的资源,然后释放空间

#include <iostream>
using namespace std;
class Test
{
public:
	Test(int data = 10)
		: _data(data)
	{
		cout << "Test():" << endl;
	}
	~Test()
	{
		cout << "~Test():" << endl;
	}
private:
	int _data;
};
int main()
{	
	// 申请单个Test类型的空间
	Test* p1 = (Test*)malloc(sizeof(Test));
	free(p1);
	// 申请10个Test类型的空间
	Test* p2 = (Test*)malloc(sizeof(Test) * 10);
	free(p2);
	// 申请单个Test类型的对象
	Test* p3 = new Test;	//申请空间并调用构造函数初始化
	delete p3;
	// 申请10个Test类型的对象
	Test* p4 = new Test[10];//申请空间并调用构造函数初始化
	delete[] p4;
	return 0;
}

在这里插入图片描述

简单解释一下:对于delete操作符先调用析构函数清理对象的资源,再释放空间,这里为了演示我只写了栈的构造函数和析构函数

#include <iostream>
using namespace std;
class Stack
{
public:
	Stack(int capacity = 4)
		:_capacity(capacity)
	{
		int* _p = new int[capacity];
		_top = 0;
		_capacity = capacity;
		cout << "Stack()" << endl;
	}
	~Stack()
	{
		delete[] _p;
		_top = _capacity = 0;
		cout << "~Stack()" << endl;
	}
private:
	int* _p;
	int _top;
	int _capacity;
};
int main()
{	
	Stack* s1=new Stack;
	delete s1;
	return 0;
}

delete先调用构造函数清理对象维护的堆区B的资源,然后再释放堆区A的空间

在这里插入图片描述

new/delete底层原理

通过汇编代码我们发现,在底层:new会调用operator new函数和Stack构造函数,而delete也会调用析构函数和operator delete函数,那么operator new和operator delete是什么函数呢?

在这里插入图片描述

在这里插入图片描述

operator new和operator delete是系统提供的两个全局函数,通过operator new申请空间,operator delete释放空间,实际上operator new也是调用malloc申请空间的,operator delete也是调用free释放空间的,那么为什么不直接用malloc和free呢?

c语言中malloc申请空间失败会返回NULL空指针,那我们检查错误的方式就是通过errno错误码,而面向对象的语言,处理错误的方式一般是抛异常,C++中也要求抛异常——try catch,

这里我简单提一下抛异常,当我们new失败后,如果不捕获异常就会抛异常

int main()
{	//由于申请空间过大new失败
	char* p = new char[1024u * 1024u * 1024u * 2 - 1];  
	printf("execute\n");
	return 0;
}

在这里插入图片描述

那我们捕获异常,new失败后编译器就会提示申请失败原因

#include <iostream>
using namespace std;
int main()
{	
	try    //检测异常
	{
		char* p = new char[1024u * 1024u * 1024u * 2 - 1];  //new失败,直接跳到catch,不执行下面语句
		printf("execute\n");
	}
	catch (const exception& e)	//捕获并处理异常
	{
		cout << e.what() << endl;
	}
	return 0;
}

在这里插入图片描述

所以这就是为什么不直接使用malloc的原因,因为malloc申请空间失败不会抛异常

而operator delete也是在free函数的基础上增加了一些检查机制

operator new/operator delete用法与malloc/free函数类似,在定位new中会介绍

C++也有operator new[]和operator delete[],仅仅是为了和new[]和delete[]配对

在这里插入图片描述

在这里插入图片描述

重载类的专属operator new和 operator delete

operator new和operator delete是可以自己定义的,一般用于在STL中的内存池中申请空间,没学过内存池的小伙伴可以简单了解一下。

有个住在山上的少年,他每次洗澡,做饭,洗衣服需要用水的时候都需要跑到山下的河边接水,然后再回到山上,每天重复多次。那么可不可以在家附近建一个小水池,一次性将水池装满水,每次用水的时候就直接从水池打水就行了,不需要每次都跑到山下。这也就是内存池出现的原理,这里山下的小河可以看作操作系统的堆区,而每次打水可以看作是从堆区申请空间,水池可以看作内存池,这样我们直接从堆区申请一大块空间放在内存池,每次需要申请时,直接到内存池申请空间就行了,不需要每次都从堆区申请空间。

在这里插入图片描述

这里以双链表为例,如果没有内存池,每次创建新的节点时都需要从堆区申请空间,频繁的找操作系统申请会大大降低效率

我们直接在类里面定义operator new和operator delete,与系统提供的两个全局的函数构成重载,到时候new和delete就会调用我们定义的operator new和delete而不会调用那两个全局的函数

struct Listnode
{
	ListNode* _next;
	ListNode* _prev;
	int _data;
	ListNode(int val = 0)
		:_next(nullptr)
		,_prev(nullptr)
		,_data(val)
	{}
	void* operator new(size_t n)
	{
		void* p = nullptr;
		p = allocator<ListNode>().allocate(1);	//allocator是STL中的内存池
		cout << "memory pool allocate" << endl;
		return p;
	}
	void operator delete(void* p)
	{
		allocator<ListNode>().deallocate((ListNode*)p, 1);
		cout << "memory pool deallocate" << endl;
	}
};
class List
{
public:
	List()
	{
		_head = new ListNode;
		_head->_next = _head;
		_head->_prev = _head;
	}
	void ListPush(int val)
	{
		ListNode* newnode = new ListNode;
		newnode->_data = val;
		ListNode* tail = _head->_prev;
		tail->_next = newnode;
		newnode->_prev = tail;
		newnode->_next = _head;
		_head->_prev = newnode;
	}
	~List()
	{
		ListNode* cur = _head->_next;
		while (cur != _head)
		{
			ListNode* next = cur->_next;
			delete cur;
			cur = next;
		}
		delete _head;
		_head = nullptr;
	}
private:
	ListNode* _head;
};
int main()
{
	List l;
	l.ListPush(1);
	l.ListPush(2);
	l.ListPush(3);
	l.ListPush(4);
	return 0;
}

在这里插入图片描述

定位new

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化

#include <iostream>

using namespace std;

class Test
{
public:
	Test(int data = 10)
		: _data(data)
	{
		cout << "Test():" << endl;
	}
	~Test()
	{
		cout << "~Test():" << endl;
	}

private:
	int _data;
};
//构造函数是不支持直接手动调用的,所以引入定位new,对已经存在的对象调用构造函数初始化,常用于内存池中的对象
int main()
{	
//调用operator new申请空间,operator new和malloc函数用法类似
	Test* p = (Test*)operator new(sizeof(Test));
	//调用构造函数初始化
	new(p)Test(1);	//定位new,new后面接要初始化对象的地址+类型,(1)表示初始化为1
	//先调用析构函数,析构函数可以直接调用
	p->~Test();
	//在调用operator delete释放空间
	operator delete (p);
	

	return 0;
}

new/delete与malloc/free区别总结

共同点:都从堆上申请空间,并需要手动释放空间,因为new/delete可以说是对malloc/free的升级,申请/释放空间还是会调用malloc/free

不同点:

1、new/delete是操作符,而malloc/free是函数

2、首先是用法上的不同,malloc返回类型是void*,需要强转,而new不需要强转了,new后面直接跟类型,new也不需要计算空间大小,申请N个加[N],new/delete配对,mew[]/delete[]配对

3、malloc失败返回NULL指针,new失败如果不catch捕获就会抛异常

4、对内置类型,new和malloc一样也不会初始化,但对自定义类型,new会先申请空间再调用构造函数初始化,delete会先调用析构函数清理对象中的资源再释放空间

内存泄漏

动态申请的内存空间不使用了,又没有主动释放,就存在内存泄漏,当然不是所有的内存泄漏都有危害。

1.出现内存泄漏的进程正常结束,会将内存还给操作系统,不会有什么危害

2.但出现内存泄漏的进程非正常结束,比如僵尸进程,或是长期运行的程序,比如服务器程序,出现内存泄漏,那么危害就很大了,系统会越来越慢,甚至是卡死宕机

所以我们在new申请空间时,一定要记得delete释放空间

了解到这里我们就明白了c++为什么要引入new/delete了

1.对自定义类型,对象动态申请空间时,new/delete会自动调用构造函数/析构函数

2.new失败后抛异常,符合面向对象语言对出错的处理机制

3.我们可以定义类专属的operator new和operator delete,这样可以从内存池申请空间,避免频繁从堆区申请空间

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程网的更多内容! 

--结束END--

本文标题: C++的动态内存管理你真的了解吗

本文链接: https://www.lsjlt.com/news/140393.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C++的动态内存管理你真的了解吗
    目录前言用法上对内置类型对自定义类型new/delete底层原理重载类的专属operator new和 operator delete定位newnew/delete与malloc/f...
    99+
    2022-11-13
  • C语言的动态内存管理你了解吗
    目录C/C++内存分配方式C++内存管理方式new和delete的使用new和delete的骚操作new和delete的区别重载new和delete定位new表达式内存泄露总结C/C...
    99+
    2022-11-13
  • C/C++的内存管理你了解嘛
    目录C/C++内存分布C语言中动态内存管理方式C++ 内存管理方式new和delete操作自定义类型new和delete的实现原理1.内置类型2.自定义类型malloc/free和n...
    99+
    2022-11-13
  • 带你了解C++的动态内存分配
    目录new与delete运算符动态分配数组动态分配字符串总结new与delete运算符 数组在定义时就规定了其长度,这使得它的内存空间也固定了下来,这称为静态内存分配。 内存申请大了...
    99+
    2022-11-12
  • 深入了解C语言的动态内存管理
    目录一、为什么会存在动态内存二、动态内存函数1.malloc和free2.calloc3.realloc三、动态内存函数常见错误2.对NULL指针进行解引用操作3.使用free释放一...
    99+
    2022-11-13
  • Java 动态代理你真的懂了吗(动态和代理)
    好几天不写文章,今天来写一篇,从之前的计划表上看到还有关于java的动态代理没写,这个技术平常用的少,也不是特别好理解,今天补上这篇,希望能讲明白,不至于像我一样迷茫好久,开始吧 动...
    99+
    2022-11-12
  • C语言的动态内存管理的深入了解
    目录一、动态内存分配二、动态内存分配函数1、malloc()2、realloc()3、calloc()三、用free函数释放内存四、迷途指针总结一、动态内存分配 (1)用malloc...
    99+
    2022-11-13
  • C++的多态和虚函数你真的了解吗
    目录一、C++的面试常考点二、阿里真题2.1 真题一(1)虚函数表vtbl(2)构造一个派生类对象的过程(3)析构一个派生类对象的过程2.2 真题二2.3 真题三2.4 真题四2.5...
    99+
    2022-11-13
  • C++中的函数你真的理解了吗
    目录1 概述2 函数的定义及调用3 值传递4 函数的常见形式5 函数的声明6 函数的分文件编写作用:让代码结构更加清晰1.2.3.4.总结1 概述 作用:将一段经常使用的代码进行封装...
    99+
    2022-11-13
  • C++中的数组你真的理解了吗
    目录1 概述2 一维数组2.1 一维数组定义方式2.2 一维数组组名2.3 冒泡排序3 二维数组3.1 二维数组定义方式3.2 二维数组数组名3.3二维数组应用举例总结1 概述 所谓...
    99+
    2022-11-13
  • C++静态变量,常量的存储位置你真的了解吗
    目录引言C++对内存的划分如何落实在Linux上自由存储区和堆之间的问题栈常量区静态存储区静态局部变量静态局部变量、静态全局变量、全局变量的异同总结引言 在动态内存的博客中,我提到:...
    99+
    2022-11-12
  • C++动态内存管理详解
    目录1.C/C++程序地址空间2.C语言动态内存管理(1)malloc(2)calloc(3)realloc(4)free3.C++动态内存管理(1)C++为什么要设计一套自己专属的...
    99+
    2022-11-12
  • Python 内存管理的工作原理,你了解吗?
    Python 为开发者提供了许多便利,其中最大的便利之一是其几乎无忧的内存管理。开发者无需手动为 Python 中的对象和数据结构分配、跟踪和释放内存。运行时会为你完成所有这些工作,因此你可以专注于解决实际问题,而不是争论机器级细节。尽管如...
    99+
    2023-05-14
    Python 内存管理
  • 内网穿透你真的了解吗?
    前言 内网穿透作为程序员常用的调试手段之一,我们可以通过在个人电脑上运行花生壳或者 frp 等方式,让他人访问我们本地启动的服务,而且这种访问可以不受局域网的限制,当我们使用ngrok,frp等开源框架时,你是否有好奇过它神奇的作用?明明没...
    99+
    2023-08-31
    网络穿透 NAT 内网 后端开发 Linux服务器开发
  • C++的运算符你真的了解吗
    目录前言1 算术运算符2 赋值运算符3 比较运算符4 逻辑运算符总结前言 运算符的作用:用于执行代码的运算 主要有: 1 算术运算符 用于处理四则运算 对于前置递增:将递增运算前...
    99+
    2022-11-13
  • Java 缓存:你真的了解它吗?
    在 Java 开发中,缓存是一个非常重要的概念。它可以大大提升应用程序的性能,减少对底层资源的占用,提高用户体验。但是,你真的了解 Java 缓存吗?本文将为大家介绍 Java 缓存的基本概念、分类、应用场景以及常见的缓存框架。 一、缓存...
    99+
    2023-10-06
    缓存 学习笔记 面试
  • C++的数据类型你真的了解吗
    目录前言1 整型2 sizeof关键字3 实型(浮点型)4 字符型5 转义字符6 字符串型7 布尔类型 bool8 数据的输入总结前言 C++不像python,创建变量的时候必须指定...
    99+
    2022-11-13
  • C++的智能指针你真的了解吗
    目录什么是RAIIRAII的原理裸指针存在的问题auto_ptrunique_ptr总结什么是RAII RAII(Resource Acquisition Is Initializa...
    99+
    2022-11-13
  • 详解C语言中的动态内存管理
    目录一、动态内存管理1.1为什么要有动态内存管理1.2动态内存介绍1.3常见的动态内存错误一、动态内存管理 1.1为什么要有动态内存管理 1.1.1  在c语言中我们普通的...
    99+
    2022-12-12
    C语言动态内存管理 C语言 内存管理 C语言 内存
  • c++动态内存管理详解(new/delete)
    目录前言用法上对内置类型对自定义类型new/delete底层原理重载类的专属operator new和 operator delete定位newnew/delete与malloc/f...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作