C++ 使用delete释放new[N]的问题

了解过C++的语法知识, 就应该知道, new 申请的空间用 delete 释放, new [] 申请的连续空间用delete [] 释放, 这样肯定是没什么问题, 是正确的. 但当我们不遵循规则时会怎样呢?

虽然在我们自己在开发过程肯定不会主动地乱用, 但保不齐哪天delete后面多写或少写个[], 如果了解过的话, 出bug了我们可以快速定位

目录

1. new [] 申请的空间用 delete 释放

系统预定义类型

自定义类型

 原理

2. new 申请的空间用 delete [] 释放

系统预定义类型

自定义类型


1. new [] 申请的空间用 delete 释放

        先来看结果, 再看原理

  • 系统预定义类型

        如int ,char, float, double 等系统预定义的类型这样操作是没有问题滴, 如下代码

#include<iostream>
using namespace std;
int main() {
	int* p = new int[10];
	for (int i = 0; i < 10; ++i) {
		p[i] = i + 1;
	}
	delete p;
	system("pause");
	return 0;
}

如下图, 在delete p之前 

        

如下图, 在执行delete p 之后, 可以看到这一片连续的空间都被释放

      

  • 自定义类型

        自定义类型如C中的结构体, 枚举, 共用体(联合体), C++中新增的类. 自定义类型这样操作是有问题滴, 会导致程序奔溃, 如下

#include<iostream>
using namespace std;
class A {
public:
	int m_a = 0;
	~A() {
		cout << "调用析构\n";
	}
};
int main() {
	A* p = new A[10];
	for (int i = 0; i < 10; ++i) {
		p[i].m_a = i + 1;
	}
	delete p;
	system("pause");
	return 0;
}

 为执行delete p 之前程序正常执行, 内存中的值也符合预期 

当执行delete p 时 ,程序只输出了一句 "调用析构" 然后异常终止. 如下图

      

  •  原理

说到原理,  就要从new/new [] 和 delete/delete [] 的原理下手了, new和delete 在底层实际是分别封装了operator new 和operator

delete这两个函数的, 而 operator new 和operator delete 在底层又分别封装了 malloc() 和 free()

free函数只有一个指针参数, 那么, free函数如何知道要释放多大的内存空间呢 ? 实际上, malloc所申请的空间大小, free是能够通

过传入的指针(必须是malloc返回的所申请空间的首地址)获取到的, 至于具体如何获取, 有多种方式, 过于底层, 这里也无需考虑, 我

们只需要知道, 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 A[10] 时, malloc本应该申请10个A类型大小的空间也就是40字节, 但此时

malloc申请了44个字节, new返回指针时malloc返回的指针往后4个字节的地址, 如下图

那么这4个字节是干什么的呢 ?? 实际上这4个字节存放着所申请类型元素的个数, 那么前面说到 free自己可以只通过传入一个指

针就知道释放多大的内存空间, 那增加这4个字节来存放元素个数岂不是多此一举, 其实并不是多此一举. 再继续来看.

A* p = new A[10] 的正确搭配是delete [] p, 那么delete[ ] 此时获取到的指针是new []的返回值, 如上图, 并不是指向malloc所申请空

间的首地址, 这样看的话底层的free并不能通过这个指针来释放掉这片空间, 但是delete[ ]会把 new所返回的指针往前4个字节位置

处的地址给free, 此时free就能能正确的释放掉这整片空间. 注意, delete [] 和new [ ] 一样都只对自定义类型才做会此有特殊操作

(当类型是预定义类型时delete []和delete没有区别),  这多出来的4个字节保存着元素个数, 但好像没用到啊, 那到底是干嘛的??

别急, 继续分析.  我们知道, 一个对象在释放空间之前, 需要调用析构函数来完成一些资源的清理工作等,  所以, delete[] p可是要连

续释放10个对象,那么就需要先调用10次析构函数分别来完成对这10个对象的资源清理工作. 那么问题来了, delete [] 又没有像

new A[10] 一样传一个参数进去, delete [] 幽怨的说: "我可不知道要调用多少次析构", new [] 说: " 我都让小弟(malloc)安排好了" .

说到这儿应该心里有数了, 多出来的4个字节存的元素个数, 就是用来让delete [] 知道要调用多少次析构函数的. 
 

了解完delete []原理, 再回到代码中具体出错的原因, new出来的10个对象, 执行delete p 时, 先调用一次析构函数完成对第一个对

象的析构, 后面9个就都不会析构了, 因为delete对应的new都是对单个元素的操作, (new返回的是malloc返回的指针, delete也只调

用一次析构),  而此时的 p并不是malloc返回的首地址 ((new [] 底层的malloc), 如上面的图), 所以delete在底层调用free时, 给

free传入了一个free无法识别的指针, free就会崩溃,

所以才会如上面的代码运行后只打印一次调用析构就奔溃了
 

小结:

1.new [] 在给自定义类型申请空间时, 会多分配4字节用来存储元素个数, 我们通过 *(int*)(p-1)就能看到或者直接查看内存也可以

看到. 所以delete用此时的new [] 返回的指针来调用自己底层的free时, free就出错了, 程序就会崩溃.

2. new [] 在给预定义类型申请空间时, 不会存储元素个数, 因为预定义类型delete []时没有析构可调用(所以没必要储存), 所以此时

new [] 返回的就是底层malloc所申请空间的首地址 .所以delete用此时的new [] 返回的指针来调用自己底层的free时, free不会出错

了, 程序也能正常运行

如图 :

直接打印查看元素个数

通过内存查看元素个数 因为电脑是小端序, 所以 0a 00 00 00 是 10, 可以看到, 后面依次存着 1, 2 ,3 , 4....10

2. new 申请的空间用 delete [] 释放

  • 系统预定义类型

前面已经详细说了delete [] 的原理了, 当类型是预定义类型时, delete[ ]和 delete 一样, 所以么得问题 .

不会出错, 如下代码

#include<iostream>
using namespace std;
int main() {
	int* p = new int;
	*p = 10;
	delete[] p;
	system("pause");
	return 0;
}

在new申请的空间未delete 之前, 可以看到p指向空间的值还是10, 如下图

当delete[] p执行之后, 可以看到, p所指向空间被释放掉了, 这片内存已经是非法内存了

f

  • 自定义类型

会出错, 原因是也是前面讲过了, 当类型是自定义类型时 delete[ ] p 会先根据p前面4字节中的元素个数来确定调用析构函数的次数,

先到用析构函数, 前四个字节的内存是非法内存, 其中的值是随机的, 所以说调用析构次数也是随机的, 通常这个数很大, 所以会调

用很多次析构, 再把接收的指针p往前4字节的地址给底层的free(注意, 往前4字节, 这四字节是没有被申请的空间). 由于free不能释

放没有被申请的空间, 所以free出错, 而且free也不能通过这个p往前4字节的地址知晓到底析构多大的空间, 也会出错 (free只能通

过malloc返回的地址知道要析构多大空间).

代码如下 :

#include<iostream>
using namespace std;
class A {
public:
	int m_a = 0;
	~A() {
		cout << "调用析构\n";
	}
};
int main() {
	A* p = new A;
	p->m_a = 10;
	delete[] p;
	system("pause");
	return 0;
}

由于p之前的这个随机值很大, 所以会调用很多次的析构函数, 当析构函数调用完了之后, free时程序就会出错, 如下图:

发布了223 篇原创文章 · 获赞 639 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_41071068/article/details/103149856