Ⅳ new 返回异常
如果开辟失败,在C语言阶段会返回一个nullptr(空指针),而C++则会抛出异常。
malloc开辟失败会返回nullptr。
new开辟失败抛出异常。
三、operator new和operator delete函数
new和delete真的那么神奇吗?其实它们是通过调用两个全局函数来实现开辟空间和释放空间的。
new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new 全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。(这里要注意operator new不是new的重载!!)
所以,用C封装的new和delete实际图解就包括这两部分:
我们通过编译器也可以看出来,new的底层确实是这样实现的:
那么,operator new和operator delete又有什么猫腻呢?
Ⅰ operator new和 malloc
operator new:该函数实际通过malloc来申请空间,它的用法和malloc很相似,不一样的地方,在于开辟失败,malloc返回nullptr而operator new函数则抛出异常。
同理,operator delete函数则是通过调用free来实现释放空间的。
总结一下malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
1、malloc和free是函数,new/delete是操作符。
2、malloc申请的空间不会初始化,new可以初始化。
3、malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[ ]中指定对象个数即可。
4、malloc的返回值是void*,在使用时必须强转,new不需要,因为new后跟的是空间的类型。
5、malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但new需要捕获异常。
6、申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。
三、定位new表达式(placement-new)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
Ⅰ使用格式
new(place_address) type 或者new(place_address) type(initializer-list)
place_address必须是一个指针,initializer-list 是类型的初始化列表。
Ⅱ使用场景
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显式构造函数。
malloc开辟的一块空间,进行显式调用构造函数初始化,这时候p3所指向的空间就可以认为是new开辟的空间,可以delete。如果对malloc开辟的空间没有定向new,也可以delete,但是会有警告。
Ⅲ 定向new的意义
有的老铁可能有困惑,为啥要有定向new,直接new初始化不就可以了?这不是脱裤子放屁,多此一举吗?
既然设计了,那么肯定有它的使用场景的。定向new主要用于池化技术,可以很好的提高效率。什么是池化技术呢?
我们一般无论是malloc还是new都是去堆上申请空间,类似于以前大家要用水都要去河边打水。而内存池就相当于我们自己在家里做一个蓄水池(malloc 一块空间),用水就可以直接使用(定向new)。这样会提高效率,我们不需要去挤着都去河边打水。类似于各项需要向堆申请空间的工作都被分配给一定空间,使用时各自去使用这么一块空间,每一块空间就是一个内存池供不同的工作使用,提高了工作效率。
四、内存泄漏
Ⅰ什么是内存泄漏
内存泄漏指因为疏忽或错误造成程序未被释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,不再使用内存时由于没有返还给操作系统并且失去对该段内存的控制,导致内存消耗越来越大,直到系统崩溃。
C/C++程序中一般我们关心两种方面的内存泄漏:
🖊堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据需要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的free或者delete删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak
🖊系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
Ⅱ内存泄漏的情景
我们可以看一下程序执行过程中,如果没有写释放空间会发生什么:
看到这里,我们发现,咦,我没有释放空间,但是程序结束它把申请的空间返还给操作系统了啊,内存泄漏好像没有什么危害吖。
但是联系现实,程序结束返还操作系统,如果程序不结束呢?比如长期运行的系统,服务器,比如我们玩的游戏服务器就是长期运行不关机的。这时如果有内存泄漏就会导致可用内存越来越少,它的表现就是cpu发热,电脑很卡,直到系统崩溃。
有时候有这样的场景,我们写了释放空间,但是可能执行不到这一步:
我们知道C++会抛出异常,如果在释放空间之前抛出异常,导致内存没有泄漏,这是不易发现且易于出错的地方。
最恐怖的不是一下子服务器挂掉,而是内存一点一点泄漏,每次泄漏一点内存,不易发现,可能过了十几天才发现服务器越来越卡,比较明显的例子就是早期的Android就是长期运行会变得越来越卡,可能就存在内存泄漏。
C++针对这些情况,官方设计了智能指针事前预防,也有一些第三方推出的内存泄露检测工具,用于事后差错。
第三方内存泄漏检测工具:
在Linux下内存泄漏检测:Linux下几款内存泄漏检测工具
在Windows下使用第三方工具:VLD工具说明
其他工具:内存泄漏工具比较
Ⅲ如何避免内存泄漏
1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放,不过可能碰到抛出异常导致走不到释放。
2. 采用RAII思想或者智能指针来管理资源。
3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
总的来说就两种解决方案:1、事前预防:智能指针
2、事后差错:内存泄漏检测工具