资讯专栏INFORMATION COLUMN

C++内存管理

libxd / 1915人阅读

摘要:对于申请内存失败,的处理是返回空指针,而的处理是抛异常对于自定义类型,会调用其构造析构函数,而不会。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

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



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

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

1.1 内存分布图

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

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


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。


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类型的空间并初始化为100delete 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;}


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!!!)

在讲解他们的底层实现原理之前需要先先介绍一下两个全局函数,分别是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;}

首先,创建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什么是内存泄漏?

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


5.3内存泄漏的危害

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

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

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

by the way,对于C++我们需要主动释放内存,但是在Java当中,不再需要主动释放内存,Java后台有垃圾回收器,接管了内存释放(所以Java写得好舒服,呜呜呜)


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

1.智能指针

2.内存泄漏检测工具

2.1在linux环境下:

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

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



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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/123915.html

相关文章

  • 03C++内存管理----new和delete

    摘要:但为了方便,有自己的动态内存管理方式。即通过和操作符进行动态内存管理。一般的程序内存泄漏危害并不是很大,因为程序进程结束以后就会被释放掉。公司内部规范使用内部实现的私有内存管理库。出问题了使用内存泄漏工具检测。 ...

    W4n9Hu1 评论0 收藏0
  • Objective-C的内存管理(1)——内存管理概述

    摘要:基于某些机制实现半自动管理这里的某些机制其实通常就是引用计数。早年,选择的是退一步,完全让程序员来管理引用计数的加减,称为,显然管理成本偏高。 概述 应用程序开发中,内存管理是个重要的话题。简单而言,语言层面的内存管理基本有三类: 1. 纯粹的手动管理 如C和曾经的C++。 char *some_string = malloc(BUFFER_SIZE); // do something...

    zzbo 评论0 收藏0
  • C++内存管理

    摘要:对于申请内存失败,的处理是返回空指针,而的处理是抛异常对于自定义类型,会调用其构造析构函数,而不会。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。 ...

    mudiyouyou 评论0 收藏0
  • 比较C++、Java、Delphi声明类对象时候的相关语法和原理

    摘要:但是在中就只是声明,但是还没有分配空间,中的才是分配了内存的。对于语言会报错这样是对的详细说明原因只能是或者是没有这个是中的语法,所以要区分和的语法和机制如果用声明了对象会报错这才是正确的语法 C++中创建对象的两种语法 在c++的类中,我如果要访问类中的成员变量或函数,有2种方法,第一种就是定义一个一个对象,如: Class A ... A aa; aa.xxx(); 另外一种就是...

    gaosboy 评论0 收藏0
  • 系统地学习C++

    摘要:本书主要围绕一系列逐渐复杂的程序问题,以及用以解决这些问题的语言特性展开讲解。你不只学到的函数和结构,也会学习到它们的设计目的和基本原理。因此我们把精力集中在最有价值的地方。本书不仅是对模板的权威解释,而且本书还深入地介绍了其他一般的思想。 C++ 入门教程(41课时) - 阿里云大学 C+...

    joyqi 评论0 收藏0

发表评论

0条评论

libxd

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<