IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++ 动态内存分配 -> 正文阅读

[C++知识库]C++ 动态内存分配

一、new/delete

1.1 为什么需要new/delete

?C语言中通过malloc/free等函数实现动态内存管理,为什么C++中还引入new/delete对动态内存管理?原因在于:malloc/free等函数不能满足对自定义类型的对象管理要求,对象在创建时要自动调用构造函数,对象在销毁前要自动调用析构函数,由于malloc/free是库函数而不是运算符,编译器不能把调用构造函数和析构函数的任务强加给它们。因为C++需要一个能够完成动态内存分配和初始化工作的运算符new,以及一个能够完成资源释放和释放内存工作的运算符delete

注意:new/delete是运算符,而不是函数

1.2 new/delete对内置类型处理

?new/delete对内置类型处理与malloc/free对内置类型处理没有本质区别,只是用法不一样。new直接返回目标类型的指针,不需要显式类型转换,而malloc返回void*,必须显式地转换为目标类型后使用

用法:
类型名* 指针变量名 = new 类型名; delete 指针变量名;
类型名* 指针变量名 = new 类型名[元素个数]; delete[] 指针变量名;

#include <iostream>

using namespace std;

int main()
{
	int* p1 = (int*)malloc(sizeof(int)); // 动态开辟1个大小为int的内存空间
	int* p2 = (int*)malloc(sizeof(int)*5); // 动态开辟5个大小为int的连续内存空间

	int* p3 = new int; // 动态开辟1个大小为int的内存空间
	int* p4 = new int[5]; // 动态开辟5个大小为int的连续内存空间

	free(p1); // 释放起始地址为p1的动态开辟内存空间
	free(p2);  // 释放起始地址为p2的动态开辟内存空间

	delete p3; // 释放单个元素内存空间
	delete[] p4; // 释放连续的申请的动态内存空间

	return 0;
}

malloc与new对内置类型都没有初始化,但可以int* p3 = new int(1); 将对应空间值初始化为1

1.3 new/delete对自定义类型处理

? malloc/free对自定义类型只会开辟/销毁空间,并不会调用构造函数/析构函数。new先开辟空间然后调用构造函数初始化,delete先调用析构函数释放资源然后销毁空间
在这里插入图片描述

#include <iostream>

using namespace std;

typedef	int ElemType; //元素类型重定义,以后元素类型发生改变只用改这里
#define initSize 4  // 顺序表初始化大小

class SeqList
{
private:
	ElemType* _data; //动态内存分配存放元素的数组
	size_t _size; //已存放元素个数
	size_t _capacity; //数组容量

public:
	SeqList(int capacity = 5)
		:_size(0)
		,_capacity(capacity)
	{
		_data = new	ElemType(capacity);
		cout << "调用构造函数" << endl;
	}

	~SeqList()
	{
		delete _data;
		_data = nullptr;
		_size = _capacity = 0;
		cout << "调用析构函数" << endl;
	}

	void InitSeq(int capacity = 5)
	{
		_data = new	ElemType(capacity);
		_size = 0;
		_capacity = capacity;
	}

	void DestorySeq()
	{
		delete _data;
		_data = nullptr;
		_size = _capacity = 0;
	}
};

int main()
{
	SeqList* s1 = (SeqList*)malloc(sizeof(SeqList));
	s1->InitSeq(); 
	// 业务处理
	s1->DestorySeq();
	free(s1);

	SeqList* s2 = new SeqList; // SeqList* s2 = new SeqList(10); 调用有参构造函数
	// 业务处理
	delete s2;
}

输出

调用构造函数
调用析构函数

?使用malloc为自定义类型分配空间需要手动调用InitSeq函数初始化,业务处理后需要调用DestorySeq函数进行资源释放然后free释放空间,比较麻烦且容易忘记初始化及释放资源。使用new/delete会自动调用构造函数/析构函数帮我们进行处理

二、new/delete 实现原理

? new运算符底层是调用 operator new函数与构造函数实现的,delete运算符底层是调用析构函数 operator delete函数实现的
在这里插入图片描述

2.1 operator new函数

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
		static const std::bad_alloc nomem;
		_RAISE(nomem);
		}
	return (p);
}

可以发现operator new函数申请空间还是调用malloc函数实现的,申请成功返回地址,申请空间失败会抛出异常。可以发现之所以用operator new而不是直接用malloc主要是对于申请失败时抛出异常

2.2 operator delete函数

operator delete为库函数,源代码如下:

void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;

	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));

	if (pUserData == NULL)
		return;

	_mlock(_HEAP_LOCK); /* block other threads */
	__TRY

		/* get a pointer to memory block header */
		pHead = pHdr(pUserData);

		/* verify block type */
		_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));

		_free_dbg(pUserData, pHead->nBlockUse);
	
	__FINALLY
		_munlock(_HEAP_LOCK); /* release other threads */
	__END_TRY_FINALLY

	return;
}

operator delete 通过调用free来释放空间的。

2.3 new/delete对内置类型实现原理

?如果申请的是内置类型的空间,new和malloc,delete和free基本类似。只是new在申请空间失败时会抛异常,malloc会返回NULL

2.4 new/delete对自定义类型实现原理

new:
● 先调用operator new函数为对象动态内存分配空间
● 然后调用构造函数对对象初始化

delete:
● 先调用析构函数释放资源
● 然后调用operator delete函数释放对象的空间

new [N]:
● 先调用operator new[ ]函数对对象动态内存分配空间 (operator new[ ]函数内调用operator new函数实现对N个对象空间分配)
● 然后调用N次构造函数对对象初始化

delete[ ]:
● 先调用N次析构函数,对N个对象释放资源
● 然后调用operator delete[ ]函数释放空间(operator delete[ ]函数内调用operator delete函数实现对N个对象空间释放)

2.5 operator new与operator delete 函数重载

?有些情况下希望动态内存空间不是来源于堆而是来源内存池,以提高效率。可以在类中对operator new与operator delete函数重载。类中对象申请/释放的是内存池空间,类外还是从堆中申请/释放

class ListNode
{
private:
	 ListNode* _next;
	 ListNode* _prev;
	 int _data;
	 
public:
	 void* operator new(size_t n)
	 {
		 void* p = nullptr;
		 p = allocator<ListNode>().allocate(1);
		 cout << "memory pool allocate" << endl;
		
		 return p;
	 }
	 
	 void operator delete(void* p)
	 {
		 allocator<ListNode>().deallocate((ListNode*)p, 1);
		 cout << "memory pool deallocate" << endl;
	 }
};

三、placement-new

? placement-new:在一块已分配的内存空间上调用构造函数初始化对象或对象数组

用法:
new (place_address) type
new (place_address) type(initializer-list)
new (place_address) type[元素个数]

placement-new主要应用场景是配合内存池使用:使用一块较大的动态内存分配空间,用来构造不同类型的对象或对象数组。

注意:placement-new构造的对象或对象数组要显式调用它们的析构函数进行资源释放,不要使用delete。因为构造起来的对象或对象数组大小并不一定等于原来申请空间大小,使用delete会造成内存泄漏或在之后释放原来申请空间出现运行时错误

#include <iostream>

using namespace std;

typedef	int ElemType; //元素类型重定义,以后元素类型发生改变只用改这里
#define initSize 4  // 顺序表初始化大小

class SeqList
{
private:
	ElemType* _data; //动态内存分配存放元素的数组
	size_t _size; //已存放元素个数
	size_t _capacity; //数组容量

public:
	SeqList(int capacity = 5)
		:_size(0)
		,_capacity(capacity)
	{
		_data = new	ElemType(capacity);
		cout << "调用构造函数" << endl;
	}

	~SeqList()
	{
		delete _data;
		_data = nullptr;
		_size = _capacity = 0;
		cout << "调用析构函数" << endl;
	}
};

int main()
{
	
	SeqList* s1 = (SeqList*)operator new(sizeof(SeqList)); // 动态内存申请大小为SeqList的空间
	new(s1)SeqList; // 调用SeqList的默认构造函数对起始地址为s1的对象初始化
	
	s1->~SeqList(); // 调用析构函数资源释放
	operator delete(s1); // 释放动态内存分配空间
	
	return 0;
}

四、C/C++ 动态内存分配对比

?C语言中使用malloc/free等函数进行动态内存分配,C++中使用new/delete运算符进行动态内存分配.

相同点:
● 都是从堆上分配空间,需要手动释放空间

不同点:
● malloc和free是函数,new和delete是运算符
● 对于自定义类型,malloc/free不会调用构造函数/析构函数,new会调用构造函数/析构函数
● malloc申请空间时,需要手动计算空间大小,new只需给类型名
● malloc的返回值为void*, 在使用时必须强转,new返回的是类型指针
● malloc申请空间失败返回NULL,new申请抛出异常

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 18:45:50  更:2022-08-19 18:47:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/11 3:34:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码