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

目录
  • 前言
  • 用法上
    • 对内置类型
    • 对自定义类型
  • 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;}#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,这样可以从内存池申请空间,避免频繁从堆区申请空间

总结

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

(0)

相关推荐

  • C/C++内存管理详情

    目录 C/C++内存管理 1. C/C++内存分布 2. C语言中动态内存管理方式 2.1 malloc/calloc/realloc和free 3. C++内存管理方式 3.1 new/delete操作内置类型 3.2 new和delete操作自定义类型 4. operator new与operator delete函数 5. new和delete的实现原理 5.1.new 5.2.delete 5.3.new 数组 5.4.delete 数组 C/C++内存管理 内存管理是C++最令人切齿痛

  • C++内存管理介绍

    目录 1 smart_ptr概述 1.1 RAII进制 1.2 智能指针 1.3 scoped_ptr 1.4 scoped_array 1.6 shared_array 1.7 weak_ptr弱指针 2 总结 前言; C++继承了C语言的指针,一直以来指针的一些问题困扰着开发人员,常见的指针问题主要有:内存泄露.野指针.访问越界等.值得庆幸的是C++标准委员会给我们提供了auto_ptr智能指针,后面又引入了share_ptr以及weak_ptr帮助我们正确和安全的使用指针,本文主要是介绍b

  • C++继承和动态内存分配

    目录 1.简介 2.派生类不用new 3.派生类使用new 文章转自微信 公众号:Coder梁(ID:Coder_LT) 1.简介 这里面有一个问题,当我们的基类使用动态内存分配,并且重新定义赋值和复制构造函数,这会对派生类的实现有什么影响呢? 我们来看两种情况: 2.派生类不用new 假设基类中使用了动态内存分配: class baseDMA {  private:      char *label;      int rating;     public:      baseDMA(cons

  • 深入理解C++中的new/delete和malloc/free动态内存管理及区别介绍

    malloc/free和new/delete的区别 malloc/free是C/C++标准库的函数:new/delete是C++操作符. malloc/free只是动态分配内存空间/释放空间:new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理资源. malloc/free需要手动计算类型大小且返回值类型为void*:new/delete可自动计算类型的大小,返回对应类型的指针. malloc/free管理内存失败会返回0:new/delete等的方式管理内存失败会抛出异常

  • 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/C++的内存管理你了解嘛

    目录 C/C++内存分布 C语言中动态内存管理方式 C++ 内存管理方式 new和delete操作自定义类型 new和delete的实现原理 1.内置类型 2.自定义类型 malloc/free和new/delete的区别 总结 C/C++内存分布 int globalVar = 1; static int staticGlobalVar = 1; void Test() { static int staticVar = 1; int localVar = 1; int num1[10] = {

  • C++中为什么要使用动态内存

    为什么要使用动态内存 1.按需分配,根据需要分配内存,不浪费 int main(void) { int money[10] = { 1, 2, 3 ,4, 5, 6, 7, 8, 9, 10 }; //工钱 int len = sizeof(money) / sizeof(money[0]); //money数组的长度 int num = 20; //人数 int *salary = 0; //薪资 //给salary指针分配num个内存 salary = new int[num]; //方式一

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

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

  • 详解C语言中的动态内存管理

    目录 一.动态内存管理 1.1为什么要有动态内存管理 1.2动态内存介绍 1.3常见的动态内存错误 一.动态内存管理 1.1为什么要有动态内存管理 1.1.1  在c语言中我们普通的内存开辟是直接在栈上进行开辟的 int i = 20;//在栈空间上开辟四个字节 int arr[10]={0}; //在栈中连续开辟四十个字节 这样开辟的特点是: (1)他所开辟的空间是固定的 (2)数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配 但对于空间的需求,我们有的时候并不知道,有可能空间

  • C语言动态内存管理的实现

    目录 1. 摘要 2. 为什么存在动态内存管理 3. 动态内存函数 3.1 malloc 3.2 free 3.3 calloc 3.4 realloc 4. 常见的动态内存错误 5. 几个经典笔试题 参考答案 6. 参考文献 1. 摘要 本文主要详解C语言中的动态内存分配 2. 为什么存在动态内存管理 我们先来看一段变量的声明: double x = 1.000000; char str[] = "abcdef"; 好的,上述变量的声明有何特点呢? 请思考一下,我的朋友. 对,没错,

  • C语言动态内存管理分析总结

    目录 什么是动态内存分配 动态内存函数的介绍 free malloc calloc realloc 动态内存管理中常见的错误 对NULL指针的解引用操作 对动态开辟空间的越界访问 对非动态开辟内存使用free释放 使用free释放一块动态开辟内存的一部分 对同一块动态内存多次释放 动态开辟内存忘记释放(内存泄漏) 一些经典的笔试题 题目1 题目2 题目3 题目4 柔性数组 柔性数组的特点 柔性数组的优势 什么是动态内存分配 我们都知道在C语言中,定义变量的时候,系统就会为这个变量分配内存空间,而

  • C语言动态内存管理介绍

    目录 前言: C 语言为内存的分配和管理提供了几个函数: 1.malloc() 用法 2.calloc() 用法 3.realloc() 与 free() 用法 前言: 简单记录一下,内存管理函数 为什么使用动态内存呢? 简单理解就是可以最大限度调用内存 用多少生成多少,不用时就释放而静止内存不能释放 动态可避免运行大程序导致内存溢出 C 语言为内存的分配和管理提供了几个函数: 头文件:<stdlib.h> 注意:void * 类型表示未确定类型的指针  1.malloc() 用法  分配一块

  • 关于C语言动态内存管理介绍

    目录 1.为什么需要动态内存分配 2.有关动态内存函数介绍 2.1 malloc和free 2.2 calloc函数 2.3 realloc函数 3. 常见的动态内存错误 3.1 对NULL指针进行解引用操作 3.2 对动态开辟空间的越界访问 3.3 对非动态开辟内存使用free释放 3.4 使用free释放一块动态开辟内存的一部分 3.5 对同一块动态内存多次释放 3.6 动态开辟内存忘记释放(内存泄漏) 总结 1.为什么需要动态内存分配 关于这个问题,我们先看看我们之前是如何开辟内存的. i

  • C语言 动态内存管理全面解析

    目录 1. 为什么存在动态内存分配 2. 动态内存函数的介绍 2.1 malloc和free 2.2 calloc 2.3 realloc 3. 常见的动态内存错误 3.1 对NULL指针的解引用操作 3.2 对动态开辟空间的越界访问 3.3 对非动态开辟内存使用free释放 3.4 使用free释放一块动态开辟内存的一部分 3.5 对同一块动态内存多次释放 1. 为什么存在动态内存分配 *动态内存开辟在堆区* 我们已经掌握的开辟内存方式是类型直接定义变量,开辟的内存是固定的,像: int a=

  • c++动态内存管理与智能指针的相关知识点

    目录 引言 一.介绍 二.shared_ptr类 make_shared函数 shared_ptr的拷贝和引用 shared_ptr自动销毁所管理的对象… 使用动态生存期的资源的类 应用举例:Blob类 定义Blob类 StrBlob的拷贝.赋值和销毁 三.直接管理内存 使用new分配内存 使用new动态分配和初始化对象 动态分配const对象 内存耗尽 使用delete释放内存 基本介绍 举例 四.shared_ptr和new结合使用 new直接初始化share_ptr 初始化时传入可调用对象

随机推荐