【C++】深入解析C/C++内存管理:new与delete的使用及原理(二)https://developer.aliyun.com/article/1617322
五、深入了解new和delete工作原理
new是个操作符,在编译时new A会转化为汇编指令调用malloc,一般来说malloc失败会返回空,由于C++是面向对象的过程,malloc失败返回空是不太合适,一般采用抛异常。全局函数operator new来封装malloc,去调正失败的返回情况。
int main() { A* p1 = new A;//operator new+1次构造 A* p2 = new A[10];//operatorn new[]+10构造 int* p3=new int[10];//operator new[](占用40个字节) delete p1;//1次析构+operator delete delete[] p2;//?次析构+operator delete delete[] p3;//operator delete return 0; }
结合汇编和代码提供的信息,提出以下问题:
- 编译器如何开始确定所需开辟空间大小
- 为什么p2指向大小为44字节空间,而不是40字节空间
- 为什么编译器知道p2需要调用10次析构函数
回答:
- 由于new属于操作符,在编译时就计算出了所需空间的大小。
- 编译器在所开辟空间位置前面,也是调用operator new函数多开四个字节,用于记录对象个数(针对自定义类型)
- 由于内置类型不需要调用析构函数,对此不需要记录对象个数,而自定义类型需要记录对象个数。delete[]需要通过对象个数才知道调用多少次析构函数。如果将析构函数注释,p2占用空间为40字节。由于编译器会自动生成析构函数,而该析构函数没有发挥占用,编译器会优化导致不需要四个字节记录对象个数,具体需要看编译器是否优化
六、malloc/free系列和new/delete系列的区别
我们将通过用法和底层特性两点说明:
共同点:
- 都是从堆上申请空间,并且需要用户手动释放
不同点:
- malloc和free属于函数,new和delete属于操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间并且传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
- malloc的返回值为void*,在使用事必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要的,但是new需要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
七、delete最好匹配使用
解析说明:图中delete没有匹配使用,导致可能报错。这里p2指向并不是申请空间的第一个位置,第一个位置是operator new[]实现存放对象个数申请的空间。由于空间是不能一块块释放,对此p2释放的位置是错误的,并且不明确需要调用多少次析构函数,可能会造成内存泄漏。如果是delete[] p2,会将p2指针偏移前面四个字节。
但是以上种种情况,导致这个问题是否报错,具体需要看编译器是否进行优化(编译器是否调用析构函数),对此我们只需要正确的使用delete就行,上面只是了解就行了
八、定位new表达式(placement -new)(了解)
定位new表达式时在已分配的原始内存空间中调用构造函数初始化一个对象。
new(指针->空间)类型() :显式调用构造函数对已经有的空间初始化
构造函数不能显式调用,析构可以显式调用(一般不会去调用两次析构的)
class A { public: A(int a = 0) : _a(a) { cout << "A():" << this << endl; } ~A() { cout << "~A():" << this << endl; } private: int _a; } / 定位new/replacement new int main() { // p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没 有执行 A* p1 = (A*)malloc(sizeof(A)); new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参 p1->~A(); free(p1); A* p2 = (A*)operator new(sizeof(A)); new(p2)A(10); p2->~A(); operator delete(p2); return 0; }
一般没有人会使用,因为这里就是把new分成两部分,那么干嘛不直接使用new更加方便。
使用场景:定位new表达式在实际中,一般是配合内存池使用。因为内存池分配出的内存没有初始化。如果是自定义类型的对象,需要使用new的定义表达式进行显式调构造函数进行初始化。
C++基本放弃了malloc/free系列。关于realloc扩容解决措施,在C++相关容器中它们会自动处理内存的扩容,使得开发者可以更加方便地使用动态大小的数据集合。
九、内存泄漏(了解)
9.1 内存泄漏概念
内存泄漏指因为疏忽或者错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费
9.2 内存泄漏的危害
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
9.3 内存泄漏分类
C/C++程序中一般我们关心两种方面的内存泄漏
1.堆内存泄漏(Heap leak)
堆内存指的是程序执行种依据须要分配通过malloc/calloc/realloc/new等从堆中分配的一块内存,用完后必须通过调用相应的free或者delete删除。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
2.系统资源泄漏
指程序使用系统分配的资源,比方套接字,文件描述符,管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!