在同花顺的笔试过程中遇到这么一个类似问题:
A* ptr = new A[10];
for(int i = 0;i < n;i++){
delete &ptr[i];
}
由此衍生出两个问题:
- new[] 申请的空间用delete释放会发生什么
- new 申请的空间用delete[] 释放会发生什么
下面就让我们来探索一下其中的奥秘
一.new[]申请的空间用delete释放
1.内置类型
如int,char,float,double等内置类型这样操作是没有什么问题的.
int main() {
//以内置类型int为例:
int* ptr = new int[10];
delete ptr;
return 0;
}
如上代码,在执行delete ptr之后,可以看到这一片连续的空间被释放.
2.自定义类型
如c语言中的结构体,枚举,联合体,C++中的类.自定义类型这样操作是有问题的.
class A {
public:
int _sum;
~A() {
cout << "调用析构" << endl;
}
};
int main() {
A* ptr = new A[10];
delete ptr;
return 0;
}
执行delete ptr时程序,只输出了一句"调用析构",便异常终止了.
原理:
new/new[] 和 delete/delete[]的原理: new和delete在底层实际是分别封装了operator new 和 operator delete两个全局函数,而operator new 和 operatordelete 在底层又分别封装了malloc() 和 free().
内置类型为何不会出错:
以上面代码中的内置类型int为例,new int[10]时底层实际是malloc申请了10个int类型数据的空间(也就是40字节).当delete时,最底层用free直接释放,显然是没有问题的.
自定义类型为何会出错:
以上面代码中的类A为例,new A[10]时,与int的处理方式不同.
对于自定义类型,new在最底层用malloc申请空间后,会调用类A的构造函数,对每个元素进行初始化等操作.
这里需要注意的是 当new[] 分配的类型时自定义类型时,new[]会让malloc在分配空间时多申请4字节,new[]返回的是底层malloc返回地址向后偏移4字节的地址.如下图所示:
具体到new int[10]时,malloc本应该申请10个A类型大小的空间,也就是40个字节,但是此时malloc实际上申请了44个字节,new返回的指针是malloc返回的指针向后偏移4个字节的地址.
这4个字节存放着所申请自定义元素的个数.
那么前面说free自己可以通过传入一个指针就知道释放多大的空间,那么增加这4个字节来存放元素个数岂不是多次一举?
很有必要讲一下delete[]的原理: delete[]会把new[]所返回的指针向前偏移4个字节的地址返回给free,因此free就能正确的释放掉整片空间.
A* ptr = new A[10]的正确搭配是delete[] ptr,那么delete[]此时获取到的指针是new[]的返回值(并不是malloc所申请空间的首地址),这样的话底层的free并不能通过这个指针释放掉这片空间,但是delete[]hi把new所返回的指针向前偏移4个字节位置的地址给free.
多出来4个字节保存着自定义元素的个数的作用:
一个对象在释放空间前需要调用析构函数来完成一些资源的清理工作.那么问题来了,delete[] 没有像new A[10]传参进去,delete[]怎么知道调用多少次析构.
其实多出来的4个字节存的元素个数,就是用来让delete[]知道调用多少次析构函数的.
了解完delete[]的原理,再回到上面代码具体出错的原因.
new出来的10个对象,执行delete ptr时,先调用一次析构函数完成对第一个对象的析构,后面9个都不会析构,因为delete对应的new都是单个元素的操作(new返回的是malloc返回的指针,delete也只调用一次析构),而此时的ptr并不是malloc返回的首地址,所有delete在底层调用free时,给free传入了一个free无法识别的指针,程序就会崩溃.(因此上面的程序运行后只打印了一次调用析构就崩溃了)
小结:
- new[]在给自定义类型空间时,会多分配4个字节用来存储元素个数.通过*(int*)(ptr-1)就能看到或者直接查看内存也可以看到.所以delete用此时new[]返回的指针来调用自己底层的free时,free就出错了,程序就会崩溃.
- new[]在给内置类型申请空间时,不会存储元素个数,因为自定义类型delete[]时没有析构函数可调用(所以没必要存储),free不会出错
二.new申请的空间用delete[]释放
1.内置类型
当类型是内置类型时,delete[]和delete一样,所以不会出现问题.
int main() {
int* ptr = new int();
delete[] ptr;
return 0;
}
2.自定义类型
会出现错误, 原因是也是前面讲过了, 当类型是自定义类型时 delete[ ] p 会先根据p前面4字节中的元素个数来确定调用析构函数的次数,
先到用析构函数, 前四个字节的内存是非法内存, 其中的值是随机的, 所以说调用析构次数也是随机的, 通常这个数很大, 所以会调用很多次析构, 再把接收的指针p往前4字节的地址给底层的free(注意, 往前4字节, 这四字节是没有被申请的空间).
由于free不能释放没有被申请的空间, 所以free出错, 而且free也不能通过这个p往前4字节的地址知晓到底释放多大的空间, 也会出错 (free只能通过malloc返回的地址知道要释放多大空间).
class A {
public:
int _sum;
~A() {
cout << "调用析构" << endl;
}
};
int main() {
A* ptr = new A();
delete[] ptr;
return 0;
}