new和delete快被问麻了,必须整理一波儿底层原理!
被翻来覆去的new和delete折磨麻了!!!
今天含泪整理一波new和delete的底层原理,希望我们再也不会被这个问题给拦住了,一把拿下它!
我们在手捧那两本经典的《C++ primer plus》和《C++ primer》书籍的时候,书上清楚地写着我们在堆中申请和释放内存的时候需要使用new和delete,new [] 和 delete [] 并且必须要配对使用。但是我们只是知道要记住,但是大多都是只知其然,而不知其所以然,那么今天就来深入的讨论讨论,为什么要配对使用,我要是不配对是使用的话会怎么样。
new和delete到底做了什么
我们先来看一个经典的图:
从图中我们可以清楚的看到:
一个C++应用程序在堆中分配内存的过程一共有三种方式:
- 应用程序—>C++标准库—>new、delete相关—>malloc和free—>HeapAlloc、VirtualAlloc(操作系统底层API)
- 应用程序—>new、delete相关—>malloc和free—>HeapAlloc、VirtualAlloc(操作系统底层API)
- 应用程序—>malloc和free—>HeapAlloc、VirtualAlloc(操作系统底层API)
- 应用程序—>HeapAlloc、VirtualAlloc(操作系统底层API)
我们可以看到以下几点:
- 操作系统的API是我们开发应用程序绕不过去的,但是直接调用操作 系统API也不是我们大多数情况需要的,因为我们更多的时候需要写出多平台的代码,而不是针对某一个操作系统来写
- 尽管我们调用new和delete,但是其本质还是调用的C语言库中使用的malloc和free两个函数(这是我们一会儿要着重关注和研究的)
一个对象的“出生入死”
Object* obj = new Object; //出生 ··· ··· delete obj //消灭
这个是我们new 一个对象然后调用delete来删除这个对象,那么其本质是什么嘞
我们new的操作在编译器眼中大致是什么样子:
Object* obj; try{ void* mem = operator new( sizeof(Object) ); //分配内存 obj = static_cast(mem); //指针转换 obj->Objcect::Object(); //原地构造 } catch( std::bad_alloc ){ //失败就不执行构造 }
operator new 本质上是分配内存,调用的是malloc函数,具体我就不详细写出来啦,感兴趣的可以在评论区一起进行交流。
我们delete的操作在编译器眼中大致是什么样子:
pc->~Object(); //先析构 operator delete(obj); //释放内存
operator delete本质上是释放内存,调用的是free函数,具体我也就不详细写出来啦。
再来看看new []和delete []
Object* obj = new Object[3]; //三次构造 ··· ··· delete[] obj; //三次析构
可以从上面看出来,根据知识迁移可知,当我们申请的是一个对象数组的时候,也同样会经历这个过程,有几个对象就会调用几次构造函数,有几个对象就会调用几次析构函数。
我们申请的内存数组会在数组的头部存储一些相关的信息,具体的大小和编译器的实现有关。但是都会记录这个数组的个数。因此,我们构造次数==析构次数
思考归纳
那么传说中的,如果我们不进行delete [] 来释放内存的话,为什么会出现内存泄漏呢?
其实本质上来说不是这样的,就算我们使用 delete 和 new [] 搭配起来,也不会在这里发生内存泄漏,因为数据头中标记了个数,所以一定会把这块儿内存free 掉的。
那内存泄漏是指的什么?
其实这样会导致这三个对象的析构函数只调用一次,也就是最上面的那个析构函数,其他的两个的析构函数不会被调用,所以现在明白了吧,内存泄漏指的是在这个对象中申请的内存,由于没有调用析构函数,导致那块儿内存不会被释放所以泄漏了内存。
如果我们new的不是对象而是内置类型,或者说对象中并不含有 指向其他堆内存的指针,那么理论上来说就算不使用delete也不会造成内存泄漏,但是我们平时还是要遵守,这是一个好的习惯,搭配起来的话就不会出现各种各样奇怪的问题了。
如果觉得有用的话,大家记得点赞和收藏哦~
微软终面题之汉诺塔:动图演示太好理解啦!
把我珍藏的学习笔记拿出来分享给大家,一起学习一起进步
快手、美团等都在问的线程池它来了
小白也能进大厂:希望我走过的路,你可以拥有捷径