c++动态内存管理详解(new/delete)

目录
  • 前言
  • 用法上
    • 对内置类型
    • 对自定义类型
  • new/delete底层原理
  • 重载类的专属operatornew和operatordelete
  • 定位new
  • new/delete与malloc/free区别总结
  • 内存泄漏
  • 总结

前言

想必大家对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,这样可以从内存池申请空间,避免频繁从堆区申请空间

总结

到此这篇关于c++动态内存管理new/delete的文章就介绍到这了,更多相关c++动态内存管理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C/C++动态分配与释放内存的区别详细解析

    1. malloc()函数1.1 malloc的全称是memory allocation,中文叫动态内存分配.原型:extern void *malloc(unsigned int num_bytes); 说明:分配长度为num_bytes字节的内存块.如果分配成功则返回指向被分配内存的指针,分配失败返回空指针NULL.当内存不再使用时,应使用free()函数将内存块释放. 1.2 void *malloc(int size); 说明:malloc 向系统申请分配指定size个字节的内存空间,返

  • c++动态内存空间示例(自定义空间类型大小和空间长度)

    动态内存空间的申请示范 利用C++的特性,能够自定义空间的类型大小和空间长度 下面这个程序是个数组动态配置的简单示例 复制代码 代码如下: #include <iostream>using namespace std; int main(){   int size = 0; cout << "请输入数组长度:";  //能够自定义的动态申请空间长度    cin >> size;    int *arr_Point = new int[size];

  • C++动态内存分配(new/new[]和delete/delete[])详解

    C++动态内存分配(new/new[]和delete/delete[])详解 为了解决这个普通的编程问题,在运行时能创建和销毁对象是基本的要求.当然,C已提供了动态内存分配函数malloc( )和free( ),以及malloc( )的变种(realloc:改变分配内存的大小,calloc:指针指向内存前初始化),这些函数在运行时从堆中(也称自由内存)分配存储单元,但是运用这些库函数需要计算需要开辟内存的大小,容易出现错误. 那么通常我们在C语言中我们开辟内存的方式如下: (void*)mall

  • C/C++ 传递动态内存的深入理解

    当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道.这些往往会使人受尽折磨.所以如果你想深入C/C++编程,你必须静下心来,好好苦一番.现在我们将讨论C/C++里我认为哪一本书都没有完全说清楚,也是涉及概念细节最多,语言中最难的技术之一的动态内存的传递.并且在软件开发中很多专业人员并不能写出相关的合格的代码.[引入] 看下面的例子,这是我们在编写库函数或者项目内的共同函数经常希望的. 复制代码 代码如下: void MyFunc(char *pReturn, size_t siz

  • 浅谈C++内存分配及变长数组的动态分配

    第一部分 C++内存分配 一.关于内存 1.内存分配方式 内存分配方式有三种: (1)从静态存储区域分配.内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在 例如全局变量,static变量. (2)在栈上创建.在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存 储单元自动被释放.栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限. (3) 从堆上分配,亦称动态内存分配.程序在运行的时候用malloc或new申请任意多少的内存,程序员

  • 关于C++动态分配内存的介绍

    介绍 操作系统中存在一个内存管理器(Memory Manager),简称MM,它负责管理内存. MM提供的服务:应用程序可以向MM申请一块指定大小的内存(借出),用完之后应用程序应该释放(还回). 如: void* p = malloc(1024);//申请,从MMM借出内存 free(p); //释放,还回MM 1.MM管理的内存区域成为堆(Heap). 2.只要应用malloc,MM都会借出,如应用不归还,MM也不会主动要求你free.如果有个应用不停地malloc,而不free,最终会用光

  • c++动态内存管理详解(new/delete)

    目录 前言 用法上 对内置类型 对自定义类型 new/delete底层原理 重载类的专属operatornew和operatordelete 定位new new/delete与malloc/free区别总结 内存泄漏 总结 前言 想必大家对c语言的动态内存分配并不陌生,忘了的小伙伴也可以看看我的这篇文章C语言动态内存分配 c语言的动态内存分配由于有些地方用起来比较麻烦同时检查错误的机制不适合c++,因此c++引入new/delete操作符进行内存管理,下面我们来深入探讨c++为什么要引入new/

  • C++动态内存管理详解

    目录 1.C/C++程序地址空间 2.C语言动态内存管理 (1)malloc (2)calloc (3)realloc (4)free 3.C++动态内存管理 (1)C++为什么要设计一套自己专属的动态内存管理方式? (2)new/delete定义 1)new/delete操作内置类型 2)new/delete操作自定义类型 (3)new/delete的实现原理 4.malloc/free和new/delete的区别 共同点: 不同点: 5.内存泄漏 总结 1.C/C++程序地址空间 计算机物理

  • C++ 动态内存分配详解(new/new[]和delete/delete[])

    一.为什么需要动态内存分配? 在C++程序中,所有内存需求都是在程序执行之前通过定义所需的变量来确定的. 但是可能存在程序的内存需求只能在运行时确定的情况. 例如,当需要的内存取决于用户输入. 在这些情况下,程序需要动态分配内存,C ++语言将运算符new和delete合成在一起. (1)特点 1.C++中通过new关键字进行动态内存申请 2.C++中的动态内存分配是基于类型进行的 3.delete关键字用于内存释放 (2)语法 ①变量申请: Type* pointer = new Type;

  • JVM内存管理之JAVA语言的内存管理详解

    引言 内存管理一直是JAVA语言自豪与骄傲的资本,它让JAVA程序员基本上可以彻底忽略与内存管理相关的细节,只专注于业务逻辑.不过世界上不存在十全十美的好事,在带来了便利的同时,也因此引入了很多令人抓狂的内存溢出和泄露的问题. 可怕的事情还不只如此,有些使用其它语言开发的程序员,给JAVA程序员扣上了一个"不懂内存"的帽子,这着实有点让人难以接受.毕竟JAVA当中没有malloc和delete.没有析构函数.没有指针,刚开始接触JAVA的程序员们又怎么可能接触内存这一部分呢,更何况有不

  • C语言与C++中内存管理详解

    目录 内存分布 动态内存管理方式-堆区 C语言动态内存管理 C++动态内存管理 new和delete的用法 operator new与operator delete函数 new和delete的实现原理 定位new表达式 高频面试题 重点new/delete和malloc/free的区别 内存泄漏 内存分布 主要段及其分布 ​ 每个程序运行起来以后,它将拥有自己独立的虚拟地址空间.这个虚拟地址空间的大小与操作系统的位数有关系.32位硬件平台的虚拟地址空间的地址可以从0~2^32-1,即0x0000

  • C++内存管理详解使用方式

    目录 c++中内存管理的方式 new和delete操作符的使用方式 operator new和operator delete函数 new和delete的原理内部实现 内置类型 自定义类型 c++中内存管理的方式 在c语言中,我们拥有malloc和free等函数可以对内存进行动态管理 但是总体来说不是很方便,所以c++拥有了一种新的方式来对内存进行管理:通过new和delete操作符来对内存进行动态分配 new和delete操作符的使用方式 new操作符的使用方式: #include<iostre

  • C语言 动态内存分配详解

    C语言 动态内存分配详解 动态内存分配涉及到堆栈的概念:堆栈是两种数据结构.堆栈都是数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除. 栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈. 堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表. \在C语言中,全局变量分配在内存中的静态存储区,非静态的局部变量(包括形参)是分配在内存的动态存储区,该存储区被

  • C语言动态内存函数详解

    目录 动态开辟空间的原因 1.malloc函数 2.free函数 3.calloc函数 4.realloc函数 总结 动态开辟空间的原因 静态开辟空间是固定的,数组在申明的时候必须指定数组的长度,在编译的时候分配内存,但是我们在实际情况中对于内存空间的需求,不仅仅是上述的情况,有时候我们需要的空间只有在运行之后才能知道,所以需要开辟一个动态内存空间,满足更多需求. 1.malloc函数 void* malloc (size_t size); malloc函数是向内存申请一块连续的空间,并返回指向

  • C语言动态内存规划详解

    目录 动态内存规划 动态内存函数的介绍 总结 动态内存规划 用C语言写程序时,因为语言的一些特性使用数组的时候只能用常量来申明数组,就导致数组的内存被卡得很死,不能根据我们的实际需求灵活的使用内存空间.有些空间的大小在程序运行时才能知道,那数组的编译时开辟空间的方式就不能满足了,这时候就只有动态开辟内存 动态内存函数的介绍 malloc函数 void* malloc(size_t size); 这个函数的 作用是向内存申请一快连续可用的空间,并返回指向这块空间的指针. 如果开辟成功,则返回一个指

  • Python深入06——python的内存管理详解

    语言的内存管理是语言设计的一个重要方面.它是决定语言性能的重要因素.无论是C语言的手工管理,还是Java的垃圾回收,都成为语言最重要的特征.这里以Python语言为例子,说明一门动态类型的.面向对象的语言的内存管理方式. 对象的内存使用 赋值语句是语言最常见的功能了.但即使是最简单的赋值语句,也可以很有内涵.Python的赋值语句就很值得研究. a = 1 整数1为一个对象.而a是一个引用.利用赋值语句,引用a指向对象1.Python是动态类型的语言(参考动态类型),对象与引用分离.Python

随机推荐