【C++】内存管理

内存管理是一个核心概念,涉及到如何在程序运行时动态地分配和释放内存。对于程序员来说,主要是通过动态内存管理来向内存申请和释放空间。在C++中,我们可以使用 n e w new new 来申请空间,使用 d e l e t e delete delete 来释放空间。


一、C/C++内存分布

C/C++ 中,内存会被分成 5 5 5 个区域,分别是堆区栈区内存映射段数据段(静态区)和代码段(常量区)。

看下面这段代码,来初步了解C/C++程序内存区域的划分:

//glovalVal:数据段(静态区)(全局数据)
int globalVar = 1;
//staticGlovalVal:数据段(静态区)(静态全局数据)
static int staticGlobalVar = 1;

void Test()
{
	//staticVal:数据段(静态区)(静态数据)
	static int staticVar = 1;
	
	//localVar:栈区(局部变量)
	int localVar = 1;
	//num1:栈区(首元素地址)
	int num1[10] = { 1, 2, 3, 4 };
	//char2:栈区(首元素地址)
	//*char2:栈区(数组)
	char char2[] = "abcd";	// 5 Byte(不要忘了'\0')
	//pChar3:栈区(首元素地址)
	//*pChar3:代码段(常量区)(常量字符串)
	const char* pChar3 = "abcd";
	
	//ptr1:栈区(地址)
	//*ptr1:堆区(动态开辟的空间)
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	int* ptr2 = (int*)calloc(4, sizeof(int));			//同ptr1
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);	//同ptr1
	
	free(ptr1);
	free(ptr3);
}

在这里插入图片描述

【说明】分区的依据是由变量的生命周期划分的

  1. 栈区( s t a c k stack stack): 又叫堆栈,存放非静态局部变量/函数参数/返回值等等。栈是向下增长的,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。(容易栈溢出 s t a c k o v e r f l o w stackoverflow stackoverflow

  2. 内存映射段: 高效的 I / O I/O I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(这里只做了解)

  3. 堆区( h e a p heap heap): 用于程序运行时动态内存分配。堆是可以向上增长的,一般由程序员分配释放, 若程序员不释放,程序结束时可能由 O S OS OS 回收 。分配方式类似于链表。

  4. 数据段(静态区)( s t a t i c static static): 存储全局数据静态数据。程序结束后由系统释放。

  5. 代码段: 存放可执行的代码/只读常量。一般存放的是函数体(类成员函数和全局函数)二进制代码。

【总结】

通过以上内容,我们了解到,程序员能够动态管理内存数据的地方是存在里的,因此我们要重点关注堆区。


二、C语言动态内存管理方式

在C语言中,我们通过 m a l l o c / c a l l o c / r e a l l o c / f r e e malloc/calloc/realloc/free malloc/calloc/realloc/free 的方式来进行动态内存管理。

【常考面试题】

Q Q Q m a l l o c / c a l l o c / r e a l l o c malloc/calloc/realloc malloc/calloc/realloc 的区别?

void Test ()
{
	//malloc/calloc/realloc的区别是什么?
	int* p2 = (int*)calloc(4, sizeof (int));		//calloc要初始化
	int* p3 = (int*)realloc(p2, sizeof(int)*10);	//realloc要扩容
	
	//这里需要free(p2)吗?
	free(p3);	//不需要,realloc分为原地扩容和异地扩容:
				//1.原地扩容:p3会覆盖p2,free(p3)相当于free(p2)
				//2.异地扩容:p3申请完空间后会自动释放p2,不需要手动释放
}

三、C++内存管理方式

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

1. 内置类型

在这里插入图片描述

void Test()
{
	//申请1个对象
	int* p1 = new int;		//int* p1 = (int*)malloc(sizeof(int));
	
	//申请10个对象(多个对象)
	int* p2 = new int[10];	//int* p2 = (int*)malloc(sizeof(int)*10);
	
	//申请1个对象 + 初始化
	int* p3 = new int(10);	//int* p3 = (int*)realloc(10,sizeof(int));

	//申请10个对象(多个对象) + 初始化
	int* p4 = new int[10]{0};			//全部初始化为0
	int* p5 = new int[10]{1,2,3,4,5}	//初始化前5个,后面值默认为0
	
	delete   p1;	//free(p1);
	delete[] p2;	//free(p2);
	delete   p3;	//free(p3);
	delete[] p4;	//free(p4);
	delete[] p5;	//free(p5);
}

我们发现:申请和释放单个元素的空间,使用 n e w new new d e l e t e delete delete 操作符;申请和释放连续的空间,使用 n e w [   ] new[\ ] new[ ] d e l e t e [   ] delete[\ ] delete[ ]注意:要匹配起来使用

2. 自定义类型

通过上面对于内置类型的操作,我们发现虽然 n e w / d e l e t e new/delete new/delete m a l l o c malloc malloc 简洁了很多而且还支持初始化,但对于 n e w / d e l e t e new/delete new/delete 的对内置类型变量的操作功能,C语言一样能实现,那么:

为什么C++还要大费周章的重新定义两个操作符呢?

因为在申请自定义类型的空间时, n e w new new 会调用构造函数, d e l e t e delete delete 会调用析构函数,而 m a l l o c malloc malloc f r e e free free 不会。

class A
{
public:
	//构造函数
	A(int a1,int a2)
		:_a1(a1)
		,_a2(a2)
	{
		cout << "A(int a)" << endl;
	}
	//析构函数
	~A()
	{
		cout << "~A()" << endl;
	}
	//拷贝构造函数
	A(const A& a)
		:_a1(a._a1)
		,_a2(a._a2)
	{
		cout << "A(const A& a)" << endl;
	}
private:
	int _a1;
	int _a2;
};

int main()
{
	A* p = new A(10,20);
	cout << endl;

	//1.定义对象初始化
	A a1(1, 1), a2(2, 2), a3(3, 3);
	A* p1 = new A[3]{ a1,a2,a3 };				//拷贝构造:A a = a1
	cout << endl;
	
	//2.匿名对象初始化
	A* p2 = new A[3]{ A(1,1),A(2,2),A(3,3) };	//匿名对象:A(1,1)
	cout << endl;
	
	//3.多参数隐式类型转换初始化
	A* p3 = new A[3]{ {1,1},{2,2},{3,3} };		//类型转换:A a = { 1,1 }
	cout << endl;

	delete p;	 cout << endl;
	delete[] p1; cout << endl;
	delete[] p2; cout << endl;
	delete[] p3; cout << endl;

	A* pm1 = (A*)malloc(sizeof(A));
	A* pm2 = (A*)malloc(sizeof(A)*10);
	free(pm1);
	free(pm2);
	
	return 0;
}

运行结果如下:

在这里插入图片描述

可以看出, n e w new new 会调用构造函数, d e l e t e delete delete 会调用析构函数,而 m a l l o c malloc malloc f r e e free free 不会调用构造函数和析构函数。

因此,在 C++ 中,不管是对于自定义类型还是内置类型来说,使用 n e w new new d e l e t e delete delete 都方便了许多。


四、new 和 delete 的底层原理

1. operator new 与 operator delete 函数(重点)

n e w new new d e l e t e delete delete 是用户进行动态内存申请和释放的操作符 o p e r a t o r   n e w operator\ new operator new o p e r a t o r   d e l e t e operator\ delete operator delete系统提供的全局函数
n e w new new 在底层调用 o p e r a t o r n e w operator new operatornew 全局函数来申请空间, d e l e t e delete delete 在底层通过 o p e r a t o r d e l e t e operator delete operatordelete 全局函数来释放空间

下面是 o p e r a t o r   n e w operator\ new operator new o p e r a t o r   d e l e t e operator\ delete operator delete 的源代码:

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;
申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
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
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}

/*
operator delete: 该函数最终是通过free来释放空间的
*/
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;
}

/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

通过上述两个全局函数的实现知道:

  1. o p e r a t o r   n e w operator\ new operator new 实际也是通过 m a l l o c malloc malloc 来申请空间的

如果 m a l l o c malloc malloc 申请空间成功就直接返回,否则执行用户提供的空间不足应对措施。
如果用户提供该措施就继续申请,否则就抛异常

  1. o p e r a t o r   d e l e t e operator\ delete operator delete 最终是通过 f r e e free free 来释放空间的

2. 实现原理

2.1 内置类型

如果申请的是内置类型的空间, n e w new new m a l l o c malloc malloc d e l e t e delete delete f r e e free free 基本类似,不同的地方是: n e w / d e l e t e new/delete new/delete 申请和释放的是单个元素的空间, n e w [   ] new[\ ] new[ ] d e l e t e [   ] delete[\ ] delete[ ] 申请的是连续空间,而且 n e w new new申请空间失败时会抛异常 m a l l o c malloc malloc 会返回 N U L L NULL NULL

2.2 自定义类型

(1)new 的原理
  1. 调用 o p e r a t o r   n e w operator\ new operator new 函数申请空间。
  2. 在申请的空间上执行构造函数,完成对象的构造。

在这里插入图片描述

(2)delete 的原理
  1. 在空间上执行析构函数,完成对象中资源的清理工作。
  2. 调用 o p e r a t o r   d e l e t e operator\ delete operator delete 函数释放对象的空间。

在这里插入图片描述

(3)new T[N] 的原理
  1. 调用 o p e r a t o r   n e w [   ] operator\ new[\ ] operator new[ ] 函数,在 o p e r a t o r   n e w [   ] operator\ new[\ ] operator new[ ] 中实际调用 o p e r a t o r   n e w operator\ new operator new 函数完成 N N N 个对象空间的申请。
  2. 在申请的空间上执行 N N N 次构造函数
(4)delete[ ] 的原理
  1. 在释放的对象空间上执行 N N N 次析构函数,完成 N N N 个对象中资源的清理。
  2. 调用 o p e r a t o r   d e l e t e [   ] operator\ delete[\ ] operator delete[ ] 释放空间,实际在 o p e r a t o r   d e l e t e [   ] operator\ delete[\ ] operator delete[ ] 中调用 o p e r a t o r   d e l e t e operator\ delete operator delete 来释放空间。

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

定位 n e w new new 表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。(显式地调用构造函数初始化

【使用格式】

n e w ( p l a c e _ a d d r e s s )   t y p e new (place\_address)\ type new(place_address) type 或者 n e w ( p l a c e _ a d d r e s s )   t y p e ( i n i t i a l i z e r − l i s t ) new (place\_address)\ type(initializer-list) new(place_address) type(initializerlist)

p l a c e _ a d d r e s s place\_address place_address 必须是一个指针 i n i t i a l i z e r − l i s t initializer-list initializerlist类型的初始化列表

【使用场景】

定位 n e w new new 表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用 n e w new new 的定义表达式进行显式地调用构造函数进行初始化。

class A
{
public:
	//构造函数
	A(int a)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	//析构函数
	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a;
};

int main()
{
	//会调用构造函数
	A* p1 = new A(1);

	//不会调用构造函数
	//p2现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	A* p2 = (A*)operator new(sizeof(A));	//底层是malloc:(A*)malloc(sizeof(A));
	
	//error C7624 : 类型名称“A”不能出现在类成员访问表达式的右侧
	//p2->A(1);	//构造函数不可以直接显式调用
	
	//显式调用构造函数(placement-new)
	new(p2)A(1);

	delete p1;
	
	p2->~A();	//析构函数可以直接显式调用
	operator delete(p2);

	return 0;
}

六、malloc/free 和 new/delete 的区别

m a l l o c / f r e e malloc/free malloc/free n e w / d e l e t e new/delete new/delete共同点是:都是从堆上申请空间,并且需要用户手动释放

表层区别使用方式上不同

  1. m a l l o c malloc malloc f r e e free free函数 n e w new new d e l e t e delete delete操作符

  2. m a l l o c malloc malloc 申请的空间不会初始化 n e w new new 可以初始化

  3. m a l l o c malloc malloc 申请空间时,需要手动计算空间大小并传递 n e w new new 只需在其后跟上空间的类型即可,如果是多个对象, [   ] [\ ] [ ]指定对象个数即可。

  4. m a l l o c malloc malloc返回值 v o i d   ∗ void\ ^* void , 在使用时必须强转 n e w new new 不需要,因为 n e w new new 后跟的是空间的类型

主要区别针对自定义类型

  1. m a l l o c malloc malloc 申请空间失败时,返回的是 N U L L NULL NULL,因此使用时必须判空 n e w new new 不需要,但是 n e w new new 需要捕获异常。(但是一般情况下不会失败)

  2. 申请自定义类型对象时, m a l l o c / f r e e malloc/free malloc/free 只会开辟空间,不会调用构造函数与析构函数,而 n e w new new 在申请空间后会调用构造函数完成对象的初始化, d e l e t e delete delete 在释放空间前会调用析构函数完成空间中资源的清理释放。


总结

以上就是对 C++ 的内存管理部分的介绍,在 C++ 中我们主要使用 n e w new new d e l e t e delete delete 进行动态内存管理,也与 C 语言的 m a l l o c malloc malloc f r e e free free 进行了比较,不仅使用起来很便捷,还能自动调用自定义类型的构造函数和析构函数。因此,即使 C++ 兼容 C 语言的 m a l l o c malloc malloc f r e e free free 的写法,我们也不会去用,我们更喜欢用 n e w new new d e l e t e delete delete 进行空间的申请和释放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值