C++内存管理

2021年11月20日 阅读数:4
这篇文章主要向大家介绍C++内存管理,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

前言:本章主要介绍C++的内存管理,以C++的内存分布做为引入,介绍C++不一样于C语言的内存管理方式(new delete对比 malloc free),最后为了加深读者的理解,会介绍new和delete的底层实现原理。linux



1.C/C++中程序内存分布

C/C++中程序内存区域大体划分为:内核空间(这部分用户不能读写)、栈、内存映射段、堆、数据段(存储全局数据、静态数据)、代码段(存储可执行代码、只读常量,又称常量区)。c++

1.1 内存分布图

image-20211118094900676

1.2 小试牛刀

接下来看下以下代码,思考下每个变量分别在哪一个内存区域?web

int globalVar = 1;
static int staticGlobalVar = 1;
void test()
{
	static int staticVar = 1;
	int localVar = 1;

	int num1[10] = { 1,2,3,4 };
	char char2[] = "abcd";
	char *pchar3 = "abcd";//有的编译器会报错,须要用const char 
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	int* ptr2 = (int*)calloc(4,sizeof(int));
	int* ptr3 = (int*)realloc(ptr2,sizeof(int) * 4);
	free(ptr1);
	free(ptr2);
}

上述代码段对应变量区域划分以下:svg

image-20211118095816959


2.C语言部分的动态内存管理方式

再来回顾一下以前C语言部分的动态内存管理方式:malloc / calloc/ realloc和free函数

带着两个问题阅读下述程序段:工具

1.malloc / calloc/ realloc的区别是什么?操作系统

2.最后须要free(p2)吗?设计

void Test()
{
	int* p1 = (int*)malloc(sizeof(int));
	free(p1);

	int* p2 = (int*)calloc(4, sizeof(int));
	int* p3 = (int*)realloc(p2, sizeof(int) * 10);

	free(p3);
}

答:3d

1.calloc至关于malloc+memset(0),即开空间+初始化。指针

2.realloc是对malloc/calloc的空间进行扩容,扩容之下又涉及到了我们前面所讲的原地扩容和异地扩容俩种情景:原地扩容p2和p3是同样的,也有多是异地扩容,那么p2指向的空间已经被释放了,因此两种状况下咱们均可以不须要处理p2。

image-20211118101324827


3.C++内存管理方式

总之就是C语言那套内存管理方式相对麻烦,因此C++提出了本身的内存管理方式:经过new和delete操做符进行动态内存管理.

3.1new/delete操做内置类型

1.开辟单个元素

开辟单个元素基本语法: type * ptr = new type(content); ,能够理解成动态申请一个type类型的空间并将其中内容初始化为content,固然,你也能够选择不初始化。

释放空间语法: delete name;

例:

int* a = new int(100);  //动态申请一个int类型的空间并初始化为100
delete a;

2.开辟n个type类型的空间

开辟n个type类型基本语法: type* name = new type[n]

删除的基本语法:delete[] name;

例:

int* ptr = new int[100];//动态申请100个int类型的空间
delete[] ptr;           //注意哦!必定要匹配哦!否则必崩溃!

3.对于内置类型,malloc/free和new/delete确实没有什么区别,两者的做用彻底同样。

例:

int main()
{
	//malloc向内存申请4个整型大小的空间
	int* p1 = (int*)malloc(sizeof(int) * 4);
	//new向内存申请4个整型大小的空间
	int* p2 = new int[4];
	//free释放掉p1申请的空间
	free(p1);
	p1 = nullptr;
	//delete释放掉p2申请的空间
	delete[] p2;
	return 0;
}

image-20211118103800232


3.2 new/delete操做自定义类型

class  Date
{
public:
	Date(int year=2021, int month=1, int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};


int main()
{
	//malloc申请十个Date空间
	Date* p1 = (Date*)malloc(sizeof(Date) * 10);
	free(p1);

	//new申请十个Date空间
	Date* p2 = new Date[10];
	delete[] p2;

	return 0;
}

区别:在申请自定义类型空间的时候,new会调用构造函数,delete会调用析构函数,而mallo和free不会哦!


4.new和delete底层实现原理(important!!!)

image-20211118121205737

在讲解他们的底层实现原理以前须要先先介绍一下两个全局函数,分别是operator newoperator delete.

new和delete是用户进行动态内存申请和释放的操做符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层经过调用operator delete全局函数来释放空间。


4.1operator new/operator delete

operator new封装了 malloc 和失败抛异常俩个部分,

下面是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
			// 若是申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}

相似的,operator delete封装了free

下面是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 new实际也是经过malloc来申请空间,若是malloc申请空间成功就直接返回,不然执行用户提供的空间不足应对措施,若是用户提供该措施就继续申请,不然就抛异常,operator delete最终是经过free来释放空间的。


4.2new和delete的实现原理

内置类型

malloc/free与new/delete在处理内置类型时并无区别,只是malloc申请空间失败时返回空指针,而new申请空间时是抛异常,new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间。

自定义类型

1.new的原理:1.调用operator new函数申请空间。2.在申请空间上执行构造函数,完成对象的初始化。

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

另外new T[N]的原理:调用operator new[]函数,在operator new[]中实际调用N次operator new函数完成N个对象空间的申请,而后在申请的空间上执行N次构造函数。**delete[]的原理:**在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理。而后调用operator delete[]释放空间,实际在operator delete[]中调用N次operator delete来释放空间。


初学者看到“delete调用析构函数,完成对象资源的清理工做,后边又调用operator delete函数释放空间”这部份内容时可能会比较混乱,这里以栈为例子详细说下:

struct Stack
{
	int* _a;
	int _top;
	int _capacity;
	Stack(int capacity = 4)
		:_a(new int[capacity])
		,_size(0)
		,_capacity(capacity)
	{
		cout << "Stack(int capacity = 4)" << endl;
	}
	~Stack()
	{
		delete _a;
		_top = _capacity = 0;
		cout << "~Stack()" << endl;
	}
};

int main()
{
	Stack st;

	Stack* ps = new Stack;
	delete ps;
	return 0;
}

image-20211118143509751

首先,建立st变量,存放在栈当中,而后调用构造函数_a申请空间(对应上图动做1)。

接着,对于ps,会先去堆上调用operator new开辟一块空间(对应上图动做2),再调用构造函数对对象进行初始化,初始化时_a又会申请空间(对应上图动做3)

最后,delete[] ps,会先调用析构函数完成对象资源的清理,即释放_ a申请的空间,而后调用operator delete释放ps申请的空间,而后调用析构函数 _ a申请的空间。(就是步骤321)


5.相关面经

5.1malloc/free与new/delete的区别

1.malloc/free是函数,而new/delete是操做符。

2.malloc申请的空间不会初始化,而new申请的空间能够初始化(内置类型new也不会初始化)。

3.malloc申请空间时须要手动计算要申请的空间的字节数,而new申请空间只须要所申请的类型便可。

4.malloc的返回值为void*,使用是须要强制类型转换,而new不须要,由于new跟的是空间的类型。

5.对于申请内存失败,malloc的处理是返回空指针NULL,而new的处理是抛异常

6.对于自定义类型,new/delete会调用其构造/析构函数,而malloc/delete不会。


5.2什么是内存泄漏?

内存泄漏指由于疏忽或错误形成程序未能释放已经再也不使用的内存的状况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,于是形成了内存的浪费。

image-20211118150110386


5.3内存泄漏的危害

若是是长期运行的程序出现内存泄漏,影响很大,如操做系统、后台服务等等,出现内存泄漏会致使响应愈来愈慢,最终卡死。

好比王者荣耀后台服务,长期运行,只有升级的时候才会停,内存泄漏会致使可用内存愈来愈少,程序愈来愈慢,甚至挂掉。

再好比物联网设备:各类智能家居、智能机器人等等,它们内存很小,也经不起内存泄漏的折腾。

by the way,对于C++咱们须要主动释放内存,可是在Java当中,再也不须要主动释放内存,Java后台有垃圾回收器,接管了内存释放(因此Java写得好舒服,呜呜呜)


5.4如何预防内存泄漏(先了解一下,后续做者再详细介绍)

1.智能指针

2.内存泄漏检测工具

2.1在linux环境下:

image-20211114145524213

2.2在Windows环境下使用第三方工具:VLD工具

img

原理:以Visual Leak Detector为例,其工做分为3步,首先在初始化注册一个钩子函数;而后在内存分配时该钩子函数被调用以记录下当时的现场;最后检查堆内存分配链表以肯定是否存在内存泄漏并将泄漏内存的现场转换成可读的形式输出。



感谢您的阅读!!!若是内容对你有帮助的话,记得给我三连(点赞、收藏、关注)——作个手有余香的人。