使用delete释放new[]的空间造成的错误分析

探讨了new和new[], delete和delete[]的区别及其在内存管理中的重要性。当使用new[]分配数组时,如果不使用对应的delete[]进行释放,会导致程序崩溃。详细解释了现代编译器如何通过额外信息头来跟踪分配的内存。

我们都被告诫,new和delete,new[]和delete[]要成对出现。如果使用delete释放new[] 申请的空间会发什么?如下:

T* p = new T [1024];

....//do something

delete p;//会发生什么?

我先告诉你,如果T是一个base type(如,int、double、char),你会发现,程序依然正确运行,不仅如此,内存空间也被正确释放。它的正确运行,让很多人对delete[]和delete的区别的探索就此终止。也难怪在国内的博客中分析delete和delete[]的原理和delete释放new[]的文章少之又少,很多相关的文章关于这个问题一句带过。

如果代码是这样的:

class A

{

    public:

    int m;

    ~A()

    {}

};

 

int main()

{

    A* ptr = new A[1024];

    delete ptr;

    return0;

 }

你可以测试一下这段代码,在VS和g++下都测试一下,看看都出现什么结果。这会增加你日后在程序调试bug的经验(我已经遭遇一次)

 


要明白这个问题,我们要知道new和new[],delete和delete[]都干什么了些什么。

不用多说new和delete。

1.new封装了C语言中的malloc函数,申请一块内存空间,如果T是用户自定义的类类型,将使用T的构造函数初始化这块内存空间上的对象。

2.delete封装了C语言中的free 函数,如果T是用户自定义的类类型,首先调用T的析构函数,再调用free释放这个对象占用的内存空间。

new[]会更复杂一些:

T* ptr = new  T [1024];

delete[] ptr;

我们注意到,没有传入任何关于ptr指针的信息给delete[]操作符运算符(operator)。最初的C++编译器版本是要求程序员传入关于指针指向空间的大小的参数,而现代的编译器虽然也接受,但多数情况会忽视它[1]!

既然没有传入参数,那编译器一定是通过某种手段获知ptr指向空间的大小。

是的,说某种手段,是因为有多种方法。在C++发展的最初,有一种关联数组的方法:T* ptr = new[s]申请一段空间后,编译器会在内存的某个地方固定的数组中存入ptr和ptr对应的空间大小s,下次在调用delete[]的时候,就会查找这个数组中的ptr对应的s。[2]

这个方法,现代的编译器已经很少见了,因为它很慢。

现代的g++、vc、icpc都使用信息头(additional information head block)的方法来管理。在new[]申请内存空间时,会多申请n个字节,这n个字节就是信息头,位于我们ptr指针向低地址偏移n个字节的地方(block = ptr - n)。每次delete[]被调用时,编译器就会去这个信息头查找申请的内存信息。

所以,调用new[]得到的东西和调用malloc得到的东西并不相同!

现在,我们分析一下

A* ptr = new A [1024];

delete ptr;

这段代码在delete的时候发生了什么:

首先,和delete任何时候一样,编译器会给delete指派A的析构函数,以函数指针的形式,delete会调用这个析构函数,作用于ptr指向的这个对象上,显然ptr之后的1023个对象不会调用析构函数,因为我们使用的是delete而不是delete[]。然后,delete执行free函数,释放申请的空间。

但是!我们new[]中调用malloc得到的指针并不是从ptr开始的,我们的程序找不到malloc分配的ptr指针,就会崩溃![3]

而为什么base type不会有问题呢?程序运行得好好的。是的,都是编译器的原因。编译器检查到,如果是base tyep,new[]操作就会省去前面的信息头,后面发生delete释放new[]的时候,free调用的ptr和malloc调用的ptr也就一致了。当然,这不一定,也有可能某些编译器,在某些时候,即便是base type它也要加上head block呢?!

所以,如果new和delete,new[]和delete[]不配对,发生什么,未定义!

 

看到这里,相信你已经明白了delete[]为什么要对应new[]了,而delete也不能和delete[]混用的原因。



#include "stdafx.h"


int _tmain(int argc, _TCHAR* argv[])
{
	int* p = new int ;	//size大小 4
	*p = 123456789;
	
	int* p2 = new int[1024] ;	//size大小 1024*4 = 4096
	while(1)
	{

	}
	return 0;
}


 

转载于:https://www.cnblogs.com/hiwoshixiaoyu/p/10035042.html

### 如何使用 `delete` 释放节点空间C++ 中,`delete` 是用于释放由 `new` 动态分配的内存的关键字。当通过指针创建了一个动态对象时,该对象不会自动销毁,因此需要显式调用 `delete` 来释放其占用的内存资源[^1]。 对于数据结构中的节点(如链表节点),通常会在类的析构函数中实现内存清理逻辑。以下是具体的操作方式: #### 使用 `delete` 清理单个节点 如果有一个指向动态分配节点的指针,则可以通过以下方法释放该节点的空间: ```cpp Node* node = new Node(); // ... 对节点进行操作 ... delete node; // 释放节点所占的内存 node = nullptr; // 将指针置为空以防止悬空指针 ``` 在此过程中,`delete` 调用了节点对象的析构函数,并释放了与其关联的内存区域。需要注意的是,在释放之前应确保没有其他指针仍然引用此内存位置,否则可能导致未定义行为或悬空指针问题[^4]。 #### 在复杂数据结构中递归释放节点 某些情况下,比如处理树形或者图状的数据结构时,可能涉及多个层次嵌套的对象实例化情况。此时可以采用递归的方式逐一清除各个子节点下的所有后代节点后再执行自身的销毁动作。例如前面提到过的前缀树(Trie),它的内部实现了类似的机制来完成整个分支路径上的全部元素回收工作[^1]: ```cpp ~Node() { for (int i = 0; i < 26; ++i) { delete nexts[i]; // 如果存在有效连接则继续深入下一层级直至叶子为止 nexts[i] = nullptr; } } ``` 这里展示了如何遍历数组类型的成员变量 `nexts[]` 并对其每一个非 NULL 成员再次运用相同的规则直到达到底部边界条件停止进一步探索从而彻底消除整棵子树的影响范围内的任何残留痕迹[^1]。 另外值得注意的一点是在实际开发场景里推荐遵循RAII(Resource Acquisition Is Initialization)原则构建自定义容器类型封装底层细节使得外部使用者无需关心具体的生命周期管理事务即可安全高效地操控这些高级抽象单元[^4]. 最后强调一点就是尽管现代标准库提供了诸如 smart pointers 这样的工具可以帮助简化许多常见场合下的手动干预需求但我们依然有必要理解基础原理以便应对那些无法单纯依赖自动化解决方案的情形发生时候能够迅速定位错误根源并采取恰当措施加以修正完善整体软件质量水平提升效率减少潜在风险隐患出现几率最大化项目成功率保障最终成果价值体现出来[^3].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值