C++ 内存管理

发布于:2024-10-17 ⋅ 阅读:(62) ⋅ 点赞:(0)

个人主页:Jason_from_China-CSDN博客

所属栏目:C++系统性学习_Jason_from_China的博客-CSDN博客

所属栏目:C++知识点的补充_Jason_from_China的博客-CSDN博客

前言

主要就是两点:

  1. C++的内存管理和C语言其实大差不差,需要了解一下栈,堆,静态区(数据段),常量区(代码段),这几个区间,需要了解这几个区间存放的都是什么
  2. 需要区分几个动态开辟的空间
  • 【C语言,malloc,realloc,】
  • 【C++,new,delete,operator new,operator delete】

内存分布情况

概念概述(主要存放的内容)

一、栈又叫堆栈
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

 

二、堆区 (heap)
内存映射段是高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux 如果没学到这块,现在只需要了解一下)。
一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS 回收。分配方式类似于链表。
主要存放的是:动态分配的对象,大型数据结构,需要长期存在的数据,实现动态数据结构

 

三、数据段 (静态区)
堆用于程序运行时动态内存分配,堆是可以上增长的。
主要存放的是:(static) 存放全局变量、静态数据。程序结束后由系统释放。

 

四、代码段
代码段 -- 可执行的代码 / 只读常量。
主要存放的是:存放函数体(类成员函数和全局函数)的二进制代码

五、对比观察图

指针在内存里面的存放

一、局部指针变量 (存放在栈区)
在函数内部定义的指针变量,如果只是指向局部变量或者作为临时用途,通常存放在栈区。例如在某个函数内部定义一个指针指向另一个局部变量:

二、全局指针变量 (存放在静态存储区)
如果定义的是全局指针变量,那么它存放在静态存储区。

三、动态分配的指针 (存放在堆区)
通过动态内存分配函数 (如 malloc、calloc、realloc 等) 分配的内存空间的指针存放在堆区。 

题目精讲

题目

图解

精讲

C++内存管理方式(关键词new和delete的使用)

概念概述

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

new的使用+delete的使用

  • new类似于malloc和realloc,都是开辟一个空间,只是new可以在开辟空间的时候选择是否需要进行初始化
  • delete类似于free,但是delete这个关键字是可以连续释放动态内存的

图解:

代码解释(这里我是解释放在下面,代码放在上面):

利用malloc创建一个空间(对比使用):

	//malloc创建一个空间
	cout << "malloc创建一个空间:" << endl;
	int* p1 = (int*)malloc(sizeof(int) * 4);
	if (p1 == NULL)
	{
		exit(1);
	}
	cout << p1;
	//释放空间
	free(p1);
	printf("\n\n");
  • 这里我们首先创建一个空间,使用malloc创建,使用free进行销毁,用来和接下来的代码进行对比。

new创建内置类型一个空间+delete销毁内置类型一个空间

	//new(初始化整形为3)
	cout << "new(初始化整形为3):" << endl;
	int* p2 = new int(3);
	cout << *p2 << endl;
	//delete(销毁开辟空间)
	delete p2;
	printf("\n\n");
  • 利用new创建一个空间,delete销毁一个空间
  • 语法,int(类型)* p2(指针变量) = new(关键字) int(3)(初始化);
  • new创建空间之后可以初始化

new创建内置类型多个空间+delete销毁内置类型多个空间

	//new(创建多个空间)
	cout << "new(创建多个空间):" << endl;
	int* p3 = new int[10] {1, 2, 3, 4};
	for (int i = 0; i < 10; i++)
	{
		cout << p3[i] << "/";
	}
	//delete释放多个对象
	delete[10] p3;
	printf("\n\n");
  • 这里是创建多个空间
  •     int* p3 = new int[10] {1, 2, 3, 4};//这里是可以进行初始化的,1,2,3,4但是这里初始化是只是初始化前四位的,因为只是前四位给了数值进行初始化
  • 销毁的时候,我们是可以直接进行多个销毁的delete[(个数)]+ 指针变量

new创建多个自定义类型的空间+delete销毁多个自定义类型的空间

#include <iostream>
using namespace std;
class A
{
public:
	A(int a = 1, int b = 1, int c = 1)
		:_a(a)
		,_b(b)
		,_c(c)
	{}
	//打印函数
	void _print()
	{
		cout << _a << "/" << _b << "/" << _c << endl;
	}
private:
	int _a;
	int _b;
	int _c;
};

//动态内存申请的使用
//new,delete
//operator new,operator delete
int main()
{

	//创建自定义类型,初始化每个自定义类型的_a
	cout << "创建自定义类型,初始化每个自定义类型的_a:" << endl;
	A* p4 = new A[10]{ 2,2,22,22,2,2,2,22,2,2 };
	for (int i = 0; i < 10; i++)
	{
		p4[i]._print();
	}
	delete[10] p4;
	printf("\n");

	return 0;
}
  • 这里new就体现的比malloc更便携的地方,这里是自定义类型的创建空间,不仅可以创建空间,还可以创建多个空间,并且进行初始化,但是这里只是对_a进行初始化
  • 这里需要注意的是, 类必须有默认构造函数,不然会导致开辟空间的失败、
  • 实在不想写默认构造也可以,就是有点麻烦
  • delete的销毁同理

new创建多个自定义类型的空间+delete销毁多个自定义类型的空间(对任意数值进行初始化):

	//创建自定义类型,初始化所有你需要的数值
	cout << "创建自定义类型,初始化所有你需要的数值:" << endl;
	A* p5 = new A[10]{ {1,2,3},{3,4,5} ,{4,5,6}, {2,2,2}, {2,22,2}, {2}, {2,2}, {2,2,2}, {}, {} };
	for (int i = 0; i < 10; i++)
	{
		p5[i]._print();
	}
	delete[10] p5;
  • 这里我想说的只有一点,就是初始化的时候我们是需要用花括号的{{}{}{}{}};

代码总结

#include <iostream>
using namespace std;
class A
{
public:
	A(int a = 1, int b = 1, int c = 1)
		:_a(a)
		,_b(b)
		,_c(c)
	{}
	//打印函数
	void _print()
	{
		cout << _a << "/" << _b << "/" << _c << endl;
	}
private:
	int _a;
	int _b;
	int _c;
};

//动态内存申请的使用
//new,delete
//operator new,operator delete
int main()
{
	//malloc创建一个空间
	cout << "malloc创建一个空间:" << endl;
	int* p1 = (int*)malloc(sizeof(int) * 4);
	if (p1 == NULL)
	{
		exit(1);
	}
	cout << p1;
	//释放空间
	free(p1);
	printf("\n\n");


	//new(初始化整形为3)
	cout << "new(初始化整形为3):" << endl;
	int* p2 = new int(3);
	cout << *p2 << endl;
	//delete(销毁开辟空间)
	delete p2;
	printf("\n\n");


	//new(创建多个空间)
	cout << "new(创建多个空间):" << endl;
	int* p3 = new int[10] {1, 2, 3, 4};
	for (int i = 0; i < 10; i++)
	{
		cout << p3[i] << "/";
	}
	//delete释放多个对象
	delete[10] p3;
	printf("\n\n");


	//创建自定义类型,初始化每个自定义类型的_a
	cout << "创建自定义类型,初始化每个自定义类型的_a:" << endl;
	A* p4 = new A[10]{ 2,2,22,22,2,2,2,22,2,2 };
	for (int i = 0; i < 10; i++)
	{
		p4[i]._print();
	}
	delete[10] p4;
	printf("\n");


	//创建自定义类型,初始化所有你需要的数值
	cout << "创建自定义类型,初始化所有你需要的数值:" << endl;
	A* p5 = new A[10]{ {1,2,3},{3,4,5} ,{4,5,6}, {2,2,2}, {2,22,2}, {2}, {2,2}, {2,2,2}, {}, {} };
	for (int i = 0; i < 10; i++)
	{
		p5[i]._print();
	}
	delete[10] p5;


	return 0;
}

new+delete和malloc+free之间的对比使用

1,malloc这里可以不需要初始化,但是new这里是需要初始化的

,2,从链表就可以看出,简单化了,malloc还需要强制类型转化一下,再sizeof一下

 operator new和operator delete的深入讲解

前言

关于operator new和operator delete我们需要明确一个概念,这两个都是一个函数,和malloc,free一样都是一个函数,但是这里需要明确的是,这里只是类似,不是一样。

operator new语法结构

语法结构

#include<iostream>
int main()
{
	//标准的分配内存的空间形式
	//分配一个内置类型,int内置类型的空间
	void* ptr1 = operator new(sizeof(int));
	//分配一个数组形式的内存空间
	void* ptr4 = operator new[](10 * sizeof(int));

	//这样的形式也是可以使用的,但是可能会出现问题,因为operator new是一个没有初始化,也就是未定义的内存空间
	//这样分配内存容易导致错误访问,所以还是建议使用标准化来分配内存空间
	//void* ptr2 = operator new(10 * sizeof(int));
	//void* ptr3 = operator new[](sizeof(int));
	//void* ptr4 = operator new[](10 * sizeof(int));

	return 0;
}

operator delete语法结构

#include<iostream>
int main()
{
	//标准的分配内存的空间形式
	//分配一个内置类型,int内置类型的空间
	void* ptr1 = operator new(sizeof(int));
	//分配一个数组形式的内存空间
	void* ptr4 = operator new[](10 * sizeof(int));

	//这样的形式也是可以使用的,但是可能会出现问题,因为operator new是一个没有初始化,也就是未定义的内存空间
	//这样分配内存容易导致错误访问,所以还是建议使用标准化来分配内存空间
	//void* ptr2 = operator new(10 * sizeof(int));
	//void* ptr3 = operator new[](sizeof(int));
	//void* ptr4 = operator new[](10 * sizeof(int));

	//operator new和delete这里是函数,所欲我们销毁的时候是函数的形式销毁,销毁的语法结构
	//释放单个内存空间
	

	//释放内存空间、标准化释放内存空间
	operator delete(ptr1);
	operator delete[](ptr4);


	return 0;
}

operator new+operator delete原理讲解

关于operator new

  • operator new我们可以看出来,其实new是operator new的一个封装,因为new在使用的时候会调用operator new
  • operator new的底层实现上面是调用malloc来实现开辟空间的


 

关于operator delete

  • 从operator delete我们可以看出来,delect本质也是对operator delete函数的封装,再严谨的讲解就是,是对free的封装,free是对free_dbg(p,_NORMAL_BLOCK)的封装
  • 所以我们可以更清晰的看出,operator delete是一个函数,不是关键字
  • delete是关键字,不是函数

operator new+operator delete和new+delete的深入对比

一、内置类型

  1. 对于内置类型,new 和 malloc、delete 和 free 基本类似。不同在于:new/delete 申请和释放单个元素空间,new []/delete [] 申请和释放连续空间;new 申请空间失败会抛异常,malloc 失败返回 NULL。
  2. 抛异常(就是告诉你哪里有错,并且继续运行程序)

二、自定义类型 new 的原理

  1. 调用 operator new 函数申请空间,底层类似 malloc(malloc 不抛异常)。
  2. 在申请的空间上执行构造函数完成对象构造。

三、自定义类型 delete 的原理

  1. 在空间上执行析构函数清理对象资源,本质类似 free 的调用。
  2. 调用 operator delete 函数释放对象空间。

四、new T [N] 的原理

  1. 调用 operator new [] 函数实际在其中调用 operator new 完成 N 个对象空间申请。
  2. 在申请的空间上执行 N 次构造函数。

五、delete [] 的原理

 
  1. 在释放的对象空间上执行 N 次析构函数,清理 N 个对象资源。
  2. 调用 operator delete [] 释放空间,实际在其中调用 operator delete。

operator new+operator delete和new+delete使用时候的注意事项

不要交错使用,很容易导致资源使用出现问题

  • operator new只是开辟空间,不会进行初始化的
  • operator delete是只是销毁空间,不会清理资源的
  • new,在开辟空间的时候会初始化并且构建资源
  • delete,销毁空间的时候会调用构造函数销毁资源
  • 知道,尽量不要交错使用就可以
#include<iostream>
using namespace std;

class A
{
public:
	A(int capacity = 4, int size = 0)
		:_Capacity(capacity)
		, _size(size)
		, _arr(nullptr)
	{
		//创建空间
		_arr = new int[_Capacity];
		printf("A()");
	}
	~A() 
	{
		//这里释放我们是需要匹配方括号,这里是释放数组形式的内容,自适应找到需要释放的内存
		delete[] _arr;
		_Capacity = 4;
		_size = 0;
		printf("~A()");

	}

private:
	int* _arr;
	int _Capacity;
	int _size;
};




//operator new创建空间,new构造
//我们需要使用 operator delete先销毁空间,再使用delete销毁资源
//并且是不能直接使用delete来进行销毁空间的,因为我们创建的空间是未定义的,new构造之后我们是会申请资源甚至空间的
//如果直接用delect销毁不使用operator delete销毁就会导致空间没有销毁
//如果只是使用operator delete销毁空间,就会导致资源没有销毁
//并且此时还应该先试用delete来销毁资源 再销毁空间
int main()
{
	A* p = new A[10];
	delete[] p;
	printf("\n");
	void* ptr1 = operator new[](10 * sizeof(A));
	//初始化,我们也可以进行定位new进行初始化,定位new里面我们会进行讲解
	for (int i = 0; i < 10; ++i) {
		new (static_cast<A*>(ptr1) + i) A(i * 10);
	}

	// 使用这些 A 对象

	// 销毁这些对象
	for (int i = 0; i < 10; ++i) {
		(static_cast<A*>(ptr1) + i)->~A();
	}
	operator delete[](ptr1);


	return 0;
}

  • operator new创建空间,new构造
  • 我们需要使用 operator delete先销毁空间,再使用delete销毁资源并且是不能直接使用delete来进行销毁空间的,因为我们创建的空间是未定义的,new构造之后我们是会申请资源甚至空间的
  • 如果直接用delect销毁不使用operator delete销毁就会导致空间没有销毁
  • 如果只是使用operator delete销毁空间,就会导致资源没有销毁
  • 并且此时还应该先使用delete来销毁资源 再使用operator delete销毁空间

定位new表达式(placement-new) (了解)

定位new主要使用的区域在于内存池,所以这里作为了解进行学习

一、在特定内存位置构造对象

  1. 可以在预先分配好的内存区域中创建对象,而不依赖于默认的内存分配机制。例如,使用operator new或其他方式分配了一块内存后,可以使用定位new在这块内存上构造对象。
  2. 语法形式为:new (pointer) Type(args...)其中pointer是指向已分配内存的指针,Type是要构造的对象类型,args...是构造函数的参数。
  3. 当使用普通的newdelete操作符时,delete会自动调用对象的析构函数并释放内存。但是对于通过定位new创建的对象,由于没有通过常规的内存分配机制,仅仅使用delete或者operator delete来释放内存不会自动调用析构函数。
  4. 此时我们发现可以用operator new开辟空间,new构造没有那么麻烦了,我们可以直接定位,可以直接看下面代码,但是这里有问题的就是不支持显示构造,但是支持显示析构

#include<iostream>
using namespace std;
class A
{
public:
	A(int capacity = 4, int size = 0)
		:_Capacity(capacity)
		, _size(size)
		, _arr(nullptr)
	{
		//创建空间
		_arr = new int[_Capacity];
		printf("A()");
	}
	~A() 
	{
		//这里释放我们是需要匹配方括号,这里是释放数组形式的内容,自适应找到需要释放的内存
		delete[] _arr;
		_Capacity = 4;
		_size = 0;
		printf("~A()");

	}

private:
	int* _arr;
	int _Capacity;
	int _size;
};

int main()
{
	//正常函数的调用
	A* p = new A[10];
	delete[] p;
	printf("\n");

	//定位的使用,operator new创建一个空间,new定位进行构造
	void* ptr1 = operator new[](10 * sizeof(A));
	new(ptr1) A[10];//定位不支持显示构造,必须有默认构造

	//delete[] ptr1;//定位是需要显示调用析构函数来进行释放资源的。直接使用delect自动释放资源是不能实现的
	for (int i = 0; i < 10; i++)
	{
		//static_cast强制类型转化关键字//<A*>转化为A类型//(ptr1) + i)->~A();循环显示调用析构函数
		(static_cast<A*>(ptr1) + i)->~A();
	}

	operator delete[](ptr1);



	return 0;
}

二、与内存池等技术结合使用

  1. 在一些高性能场景下,为了避免频繁的内存分配和释放开销,可以使用内存池预先分配一大块内存,然后在需要创建对象时使用定位new在内存池中选取合适的位置构造对象。
  2. 这样可以提高内存分配的效率,减少内存碎片的产生。

三、资源管理和对象生命周期控制

  1. 通过定位new,可以更精细地控制对象的构造和析构时机,特别是在一些复杂的资源管理场景中。
  2. 例如,可以在特定的资源初始化后,在与之相关的内存位置构造对象,确保资源和对象的生命周期紧密关联。

四、注意事项

  1. 使用定位new构造的对象,在销毁时需要手动调用析构函数,而不能直接使用delete来释放内存,因为delete会尝试释放由默认内存分配机制分配的内存,而不是定位new所使用的内存。
  2. 例如:new (ptr) Type(args...);构造的对象,在销毁时应该使用ptr->~Type();来调用析构函数。

malloc/free 和 new/delete 的区别


今日签到

点亮在社区的每一天
去签到