C++ 内存管理原理分析

 更新时间:2021年11月19日 17:06:36   作者:林慢慢脑瓜子嗡嗡的  
本章主要介绍C++的内存管理,以C++的内存分布作为引入,介绍C++不同于C语言的内存管理方式(new delete对比 malloc free),最后为了加深读者的理解,会介绍new和delete的底层实现原理

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

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

1.1 内存分布图

image-20211118094900676

1.2 小试牛刀

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

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);
}

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

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);
}

答:

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步,首先在初始化注册一个钩子函数;然后在内存分配时该钩子函数被调用以记录下当时的现场;最后检查堆内存分配链表以确定是否存在内存泄漏并将泄漏内存的现场转换成可读的形式输出。

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

到此这篇关于C++ 内存管理原理分析的文章就介绍到这了,更多相关C++ 内存管理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++数据结构二叉搜索树的实现应用与分析

    C++数据结构二叉搜索树的实现应用与分析

    从这篇博客开始,我就要和大家介绍有关二叉搜索树的知识,它还衍生出了两棵树——AVL树和红黑树,在后面两篇博客我都会介绍。今天先从二叉搜索树开始引入
    2022-02-02
  • C++使用ZLIB库实现目录压缩与解压功能

    C++使用ZLIB库实现目录压缩与解压功能

    在软件开发和数据处理中,对数据进行高效的压缩和解压缩是一项重要的任务,这不仅有助于减小数据在网络传输和存储中的占用空间,还能提高系统的性能和响应速度,本文将介绍如何使用 zlib 库进行数据的压缩和解压缩,以及如何保存和读取压缩后的文件
    2025-02-02
  • C语言预编译#define(预处理)

    C语言预编译#define(预处理)

    这篇文章主要介绍了C语言预编译#define(预处理),#define 机制包括了一个机制,允许把参数替换到文本中,这种实现通常称为宏或者宏定义,下文更多的相关资料介绍需要的小伙伴可以参考一下
    2022-04-04
  • 对for循环中表达式和循环体的执行顺序详解

    对for循环中表达式和循环体的执行顺序详解

    今天小编就为大家分享一篇对for循环中表达式和循环体的执行顺序详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-06-06
  • 详解如何将c语言文件打包成exe可执行程序

    详解如何将c语言文件打包成exe可执行程序

    这篇文章主要介绍了详解如何将c语言文件打包成exe可执行程序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • c语言读取csv文件和c++读取csv文件示例分享

    c语言读取csv文件和c++读取csv文件示例分享

    这篇文章主要介绍了c语言读取csv文件和c++读取csv文件示例,需要的朋友可以参考下
    2014-03-03
  • C语言二叉树常见操作详解【前序,中序,后序,层次遍历及非递归查找,统计个数,比较,求深度】

    C语言二叉树常见操作详解【前序,中序,后序,层次遍历及非递归查找,统计个数,比较,求深度】

    这篇文章主要介绍了C语言二叉树常见操作,结合实例形式详细分析了基于C语言的二叉树前序,中序,后序,层次遍历及非递归查找,统计个数,比较,求深度等相关操作技巧与注意事项,需要的朋友可以参考下
    2018-04-04
  • 浅析C++中的函数重载

    浅析C++中的函数重载

    这篇文章主要介绍了浅析C++中的函数重载,在C++中,可以为两个或两个以上的函数提供相同的函数名称,只要参数类型不同,或者参数类型相同而参数个数不同,又或者参数类型参数个数相同,参数次序不同,称为函数重载,需要的朋友可以参考下
    2023-08-08
  • C语言学生成绩管理系统源码

    C语言学生成绩管理系统源码

    这篇文章主要为大家详细介绍了C语言学生成绩管理系统源码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • C语言的冒泡排序和快速排序算法使用实例

    C语言的冒泡排序和快速排序算法使用实例

    这篇文章主要介绍了C语言的冒泡排序和快速排序算法使用实例,示例题目也是ACM练习当中的基础习题,需要的朋友可以参考下
    2015-08-08

最新评论