C/C++ 内存管理
1. C/C++内存分布
我们先来看下面的一段代码和相关问题
- char2 只是一个字符数组,因此存放在栈上。
- 星char2 在 栈上,因为数组名sizeof的时候代表整个数组,但是进行运算的时候,比如说解引用 它是代表首元素的地址。
图示:
3.PChar3在栈上,首先const限制*PChar3,不能通过PChar3来修改PChar3指向的空间内容,但是指针变量PChar3可以改变,因此PChar3不可能在常量区。 - *PChar3 在常量区,原因如下:
4.指针变量ptr1在栈上,指向的内容在堆上,因此 * ptr 在堆上。
【说明】 - 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
- 堆用于程序运行时动态内存分配,堆是可以上增长的。
- 数据段–存储全局数据和静态数据。
- 代码段–可执行的代码/只读常量。
2. C语言中动态内存管理方式:malloc/calloc/realloc/free
【面试题】
- malloc/calloc/realloc的区别?
malloc 函数用于分配指定大小的内存块,它从堆(heap)中分配内存,但不初始化这些内存。calloc 会将分配的内存初始化为零。realloc 函数用于扩容。 - malloc的实现原理? glibc中malloc实现原理
3. C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
3.1 new/delete操作内置类型
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。
例子:
new默认不初始化,但是它也可以初始化,如下:
注意 :对于内置类型开空间使用malloc或者new几乎是一样的。
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
3.2 new和delete操作自定义类型
注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。
4. operator new与operator delete函数(重要点进行讲解)
4.1 operator new与operator delete函数(重点)
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
上面会有同学有疑问为什么要operator new,operator delete呢?直接调malloc不香吗?为啥要搞这个东西?有个原因在。首先malloc 失败的结果是什么?是返回空。所有以前用malloc的时候都会检查是否为空。new 呢? C++引出了一个叫异常的东西。通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
下面给你们看个new好用的代码:
为什么不是malloc+构造函数,而是operator new+函数构造,如果operator new 开空间失败了,malloc是返回0。operator new是封装(实际就是封装malloc),所以new失败抛异常实际上是在operator new开空间时(malloc+失败抛异常),从表层角度看malloc是一个函数的用法,new是一个操作符的用法。从底层角度看,new就是malloc,但是它在malloc的基础上加了很多东西,new其实比malloc多做了很多事情,多做了构造函数,失败了要抛异常。
还有个问题就是 new ,delete ,malloc ,free能不能混着使用,我这里建议大家配对使用。因为有时候能对,有时候可能程序会崩溃。混合使用可能导致释放位置不对。
5. new和delete的实现原理
5.1 内置类型
5.2 自定义类型
代码:
new底层原理图示:
delete底层原理图示:
析构 和 operator delete 释放的是不是同一个东西? 答案是:不是,operator delete delete的是谁?是p2指向的这块空间析构,析构是这个A对象上的资源的清理(当然是A对象有资源才需要清理,没有资源要清理,调用析构函数,就直接执行函数体的代码,比如说就只打印了一句)。
6. 定位new表达式(placement-new) (了解)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
7. 常见面试题
7.1 malloc/free和new/delete的区别
7.2 内存泄漏
7.2.1 什么是内存泄漏,内存泄漏的危害
7.2.2 内存泄漏分类(了解)
7.2.3 如何检测内存泄漏(了解)
在vs下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks() 函数进行简单检测,该函数只报出了大概泄漏了多少个字节,没有其他更准确的位置信息。
因此写代码时一定要小心,尤其是动态内存操作时,一定要记着释放。但有些情况下总是防不胜防,简单的可以采用上述方式快速定位下。如果工程比较大,内存泄漏位置比较多,不太好查时一般都是借助第三方内存泄漏检测工具处理的。
- 在linux下内存泄漏检测:linux下几款内存泄漏检测工具
- 在windows下使用第三方工具:VLD工具说明
- 其他工具:内存泄漏工具比较
7.2.4如何避免内存泄漏
- 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
- 采用RAII思想或者智能指针来管理资源。
- 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
- 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
总结一下:
内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。
完