3.new和delete不匹配问题(了解)
案例:不匹配现象
//1. int* ptr1 = new int; delete[] ptr1; //2. int* ptr2 = new int[10]; delete ptr2; //3. int* ptr3 = new int; free(ptr3);
不匹配后果:未定义,由于环境(linux还是windows或者不同编译器)不同,结果不同,不要尝试不匹配
ps:这个问题不是内存泄漏问题(内存泄漏是不会报错的,类似一种慢性病,报错类似一种急性病)
4.new的底层机制(了解)
new的底层机制其实是调用operator new函数申请空间 + 调用构造函数初始化
而operator new申请空间的底层实现也是调用malloc, 所以new的效率并没有比malloc高
封装malloc,申请内存失败,抛异常
封装malloc只是为了符合面向对象处理出现错误的处理方式—抛异常
我们其实可以手动调用operator new函数
image-20221105190435366
ps:operator new函数的使用方式和malloc一样,唯一不同的是operator new开空间失败不会返回nullptr,而是抛异常.
给大家看一下调用new的时候的反汇编:
内置类型
int main() { int* a = new int; return 0; }
这个call调用的是operator new函数
- 自定义类型
struct ListNode { int _val; ListNode* _next; ListNode(int val = 0) :_val(val) ,_next(nullptr) {} }; int main() { //创建链表 ListNode* n1 = new ListNode(1); return 0; }
第一个call是调用operator new函数
第二个call是调用构造函数
同理就有operator new[]函数,调用多次operator new
还有operator delete和operator delete[]函数
ps:我们知道new的底层机制,但是我们没有必要使用operator new去实际编程.
5.定位new表达式(了解)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
构造函数有点不一样,在我们之前学的都不能显式调用,但是定位new表达式就可以完成显式调用
ps:析构函数可以显式调用(下图证明)
class A { public: A(int a = 10) :_a(a) { cout << "构造函数" << endl; } ~A() { cout << "析构函数" << endl; } private: int _a; }; int main() { A* ptr1 = (A*)malloc(sizeof(A)); if (ptr1 == nullptr) { perror("malloc fail"); exit(-1); } //定位New--- 对ptr1指向的这块空间,显示调用构造函数初始化 new(ptr1)A(1); //ps:析构函数可以显式调用 ptr1->~A(); free(ptr1); //上面两行相当于delete ptr1; //上节课讲过delete等同于 调用析构函数+operator delete(失败抛异常) return 0; }
定位new案例:
我们听说过内存池还有池化技术,那我百度了一下,我就给大家讲一下我的理解:
举一个例子:
山上有好多和尚,他们每天需要来山脚下挑已经过滤好的自来水喝,
但每一次都要排老长老长的队,于是各个和尚都在自己家里建水池蓄水,可以避免每天排队,提高效率
于此同时也产生一个问题:蓄水池的水需要一个过滤装置定时过滤杂质后才能饮用
上述的山脚下的自来水就类似new/malloc,挑山脚下的别人已经过滤好的纯净水就是调用new/malloc开辟空间并且开好的空间是已经初始化好的,
于是和尚建蓄水池蓄水就是建内存池,提高效率
内存池的水需要定时过滤就类似定位new,对内存池的空间进行初始化
三.面试题
1.new/delete和malloc/free的区别(理解)
最大的区别是new/delete对于自定义类型能够自动调用构造函数和析构函数
2.内存泄漏
ps:内存泄漏是指针丢了,而不是内存丢了(内存一直都在)—–-指针丢了就是找不到这块空间了
(想想永不关闭的程序,比如后台服务器就知道危害了)
内存泄漏指由于疏忽或者错误造成程序未能释放已经不再使用的内存的情况
并不是指物理上的消失,而是失去了对这段内存的控制,从而造成了内存的浪费.