您现在的位置是:首页 >技术杂谈 >C&C++——深入探究动态内存管理网站首页技术杂谈

C&C++——深入探究动态内存管理

暮雨清秋.L 2024-06-18 00:01:02
简介C&C++——深入探究动态内存管理

总述

俗话说,没有理解过底层的c&c++程序员一定不是个优秀的程序员(doge),因此,在本篇博客中,将会大体地讲述一些c&c++程序内存管理的部分知识,并且再浅谈一下c++中是新增的操作(new,delete)如何进行动态内存管理的。

C&C++程序内存区域划分

首先我们需要了解一下C/C++程序中的数据是如何进行分布的,看下面这张图:
在这里插入图片描述

  1. 又叫做堆栈,用于存放非静态局部变量/函数参数/返回值等等,栈是向下生长的(朝内存地址减小的方向生长)
  • 在c/c++内存分配中,对于栈来说其数据是通过编译器自动进行管理的,无需我们手动控制,编译器会自动释放内存
  • 栈的空间很小,一般只有几MB的大小。
  1. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间的通信。
  2. 用于程序运行时的动态内存分配,堆是向上生长的(朝内存地址增大的方向增长)

区别于栈,堆中的内存管理需要程序员控制,因此需要避免内存泄漏的问题。

  1. 数据段(静态区): 存储全局数据和静态数据
  2. 代码段(常量区): 存储可执行的代码/只读常量

C++内存管理方式

在C语言中,我们进行动态内存管理是用<stdlib.h>库文件中的malloc/calloc/realloc/free进行的,这些方式在C++中当然可以继续使用,但是有些地方就显得无能为力了,而且使用起来比较麻烦,因此C++也制作了一套自己的内存管理方式: 通过new和delete操作符进行动态内存管理

运用new/delete操作内置类型

看下面代码:

void Test()
{
	//动态申请一个int类型空间
	int* ptr = new int;
	//动态申请一个int类型的空间并将其初始化为10
	int* ptr2 = new int(10);
	//动态申请10个int类型的空间
	int* ptr3 = new int[10];
	
	//释放空间
	delete ptr;
	delete ptr2;
	delete ptr3;
}

是的,使用方法就是 new 类型名,在类型名之后括号内的内容表示对其进行初始化,而[]表示初始化对象的个数。

**注意:**申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],切记一定要匹配起来使用!!!

new和delete操作自定义类型

对于自定义类型,new/delete和malloc/free差别确实不大,但是对于自定义类型来说,可以说new/delete简直是为自定义类型量身定做的!

看下面的代码:

#include <iostream>
#include <cstdlib>
using namespace std;
class A
{
private:
	int _a;
	int* _parr;
public:
	A(int a = 0)
		:_a(a)
		,_parr(new int[10])
	{
		cout << "A(int a = 0)" << endl;
	}
	~A()
	{
		delete[] _parr;
		cout << "~A()" << endl;
	}
	void set()
	{
		_parr = new int[10];
	}
};

int main()
{
	//new/delete和malloc/free最大的区别就是
	//对于自定义类型除了开空间还会调用构造和析构函数


	A* Pa1 = (A*)malloc(sizeof(A));
	//malloc不会自动调用构造函数,必须得自己手动实现一个
	Pa1->set();

	A* pa2 = new A(10);
	//new支持分配空间时同时初始化

	A* pa3 = new A;
	//若申请空间时不进行初始化,将会调用默认构造函数

	Pa1->~A();
	//使用free之前需要自己调用析构函数,防止内存泄露
	free(Pa1);

	delete pa2;
	delete pa3;
	//使用delete在释放空间之前会先调用pa2的析构函数
	return 0;
}

该代码运行结果如下:
在这里插入图片描述

是的,在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc和free不会

operator new与operator delete函数

new 和delete 是用户进行动态内存申请和释放的操作符operator new 和 operator delete是系统提供的全局函数,new在底层调用operator new来申请空间,delete在底层调用operator delete全局函数来释放空间。
下面是库中operator new和 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)

通过观察上述两个全局函数的实现知道,operator new实际上也是通过malloc来申请空间,如果malloc申请成功就直接返回,否则执行用户提供的空间不足的应对策略,如果用户提供该策略就继续申请,否则抛异常。operator delete最终也是通过free来释放空间的。


可能有人会问,为什么不直接底层用malloc和free来实现new和delete操作符,而还要再封装一层operator new和operator delete函数呢?

那是因为c++语言中对于程序运行中出现的问题采用的是抛异常的方法,而对于c语言内置的malloc和free函数,其在内存申请失败仅仅返回空指针,这有点不符合c++的要求,所以才需要在malloc的基础上再次进行了封装。

new和delete操作符的实现原理

对于内置类型来说,new/delete和malloc/free基本是相似的,需要注意的一点是:
new/delete申请和释放的是单个元素的空间,而new[]和delete[]申请的是连续空间。


自定义类型

  • new的原理
    1. 调用operator new函数申请空间
    2. 在申请的空间上执行构造函数,完成对象的构造
  • delete的原理
    1. 在空间上执行析构函数,完成对象中资源的清理。
    2. 调用operator delete函数释放对象的空间。

在这里插入图片描述

  • new T[N]的原理
    1. 调用operator new[]函数,在operator new[]中世纪调用operator new函数完成N个对象空间的申请
    2. 在申请的空间上执行N次构造函数
  • delete[] 同理

通过观察反汇编,可以看到new操作符确实调用了operator new函数和构造函数
在这里插入图片描述

malloc/free 和 new/delete 的区别

最后,我们来总结一下这两类的区别把!

共同点: 都是从堆上申请空间,并且需要用户手动释放。

不同点:

  1. malloc 和 free 是函数,new 和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间是需要手动计算空间大小并传递,new只需在其后跟上空间类型即可,如果是多个对象,[]中指定对象个数即可
  4. malloc的返回值为void*,在使用是必须进行强转,new不需要
  5. malloc申请空间失败是返回NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数和析构函数,而new在折你去哪个空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。

以上便是所有区别啦,对于这些区别,大家一定不要死记硬背,而是要理解的去记忆,这样才能有更好的效果!


对于c&c++内存管理方面的知识就到此结束啦!如果博主有哪些地方没有讲明白或者有错误的话,欢迎评论区指出哦!?

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。