前言
在我们日常写代码的过程中,我们对内存空间的需求有时候在程序运行的时候才能知道,这时候我们就需要使用动态开辟内存的方法。
1、C/C++程序的内存开辟
首先我们先了解一下C/C++程序内存分配的几个区域:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
- 1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
- 2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
- 3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
这幅图中,我们可以发现普通的局部变量是在栈上分配空间的,在栈区中创建的变量出了作用域去就会自动销毁。但是被static修饰的变量是存放在数据段(静态区),在数据段上创建的变量直到程序结束才销毁,所以数据段上的数据生命周期变长了。
2.C语言中动态内存管理方式:malloc/calloc/realloc/free
在C语言中,我们经常会用到malloc,calloc和realloc来进行动态的开辟内存;同时,C语言还提供了一个函数free,专门用来做动态内存的释放和回收。其中他们三个的区别也是我们需要特别所强调区别的。
2.1malloc、calloc、realloc区别?
malloc函数是向内存申请一块连续可用的空间,并返回指向这块空间的指针。
calloc与malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为0。
realloc函数可以做到对动态开辟内存大小的调整。
我们通过这三个函数的定义也可以进行功能的区分:
1 2 3 4 5 6 7 8 9 |
|
3.C++内存管理方式
我们都知道,C++语言是兼容C语言的,因此C语言中内存管理方式在C++中可以继续使用。但是有些地方就无能为力了,并且使用起来也可能比较麻烦。因此,C++拥有自己的内管管理方式:通过new和delete操作符进行动态内存管理。
3.1 new/delete操作内置类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
我们首先通过画图分析进行剖析代码:
我们在监视窗口看看这3个变量
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],要匹配起来使用。
3.2 new和delete操作自定义类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
在这段代码中,p1是我们使用malloc开辟的,p2是通过new来开辟的。我们编译运行这段代码。
发现输出了这两句,那这两句是谁调用的呢?我们通过调试逐语句来分析这个过程
内置类型区别
注意:在申请自定义类型的空间时,new会自动调用构造函数,delete时会调用析构函数,而malloc和free不会。
3.3new和malloc处理失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
我们能够发现,malloc失败时会返回空指针,而new失败时,会抛出异常。
4.operator new与operator delete函数
4.1 operator new与operator delete函数
C++标准库还提供了operator new和operator delete函数,但是这两个函数并不是对new和delete的重载,operator new和operator delete是两个库函数。(这里C++大佬设计时这样取名确实很容易混淆)
4.1.1 我们看看operator new库里面的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
库里面operator new的作用是封装了malloc,如果malloc失败,抛出异常。
4.1.2 operator delete库里面的源码
该函数最终是通过free来释放空间的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
4.1.3 operator new和operator delete的价值(重点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
我们使用new的时候,new要开空间,要调用构造函数。new可以转换成call malloc,call 构造函数。但是call malloc 一旦失败,会返回空指针或者错误码。在面向对象的语言中更喜欢使用异常。而operator new相比较malloc的不同就在于如果一旦失败会抛出异常,因此new的底层实现是调用operator new,operator new会调用malloc(如果失败抛出异常),再调用构造函数。
我们通过汇编看一下ps3
operator delete同理。
总结:通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
4.2 重载operator new 与 operator delete(了解)
专属的operator new技术,提高效率。应用:内存池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
注意:一般情况下不需要对 operator new 和 operator delete进行重载,除非在申请和释放空间时候有某些特殊的需求。比如:在使用new和delete申请和释放空间时,打印一些日志信息,可以简单帮助用户来检测是否存在内存泄漏。
5.new 和 delete 的实现原理
5.1 内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
5.2 自定义类型
5.2.1 new原理
- 1、调用operator new函数申请空间
- 2、再调用构造函数,完成对对象的构造。
5.2.2 delete原理
- 1、先调用析构函数,完成对对象中资源的清理工作。
- 2、调用operator delete函数释放对象的空间
5.2.3 new T[N]原理
- 1、先调用operator new[]函数,在operator new[]中世纪调用operator new函数完成N个对象空间的申请
- 2、在申请的空间上执行N次构造函数
5.2.4 delete[]原理
- 1、在释放的对象空间上执行N次析构函数,完成对N个对象中资源的清理
- 2、调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间。