相关文章推荐
热心的砖头  ·  没有指针,Python如何实现链表、二叉树这 ...·  2 月前    · 
正直的键盘  ·  Python 有指针吗?_python中有没有指针·  2 月前    · 
非常酷的红茶  ·  Python3中的“指针” - DECHIN ·  2 月前    · 
有胆有识的斑马  ·  Python ...·  2 月前    · 
深情的楼房  ·  xampp本机访问解析php超级慢的原因之一 ...·  1 年前    · 
温柔的野马  ·  token ...·  1 年前    · 
重情义的西红柿  ·  如何启用日志以查看在Tomcat中为什么sp ...·  1 年前    · 
独立的柚子  ·  学习ASP.NET Core ...·  2 年前    · 
大气的香蕉  ·  jupyter ...·  2 年前    · 
Code  ›  C++编程经验(7):delete之后到底要不要置空?开发者社区
c++ delete 指针
https://cloud.tencent.com/developer/article/1879316
跑龙套的太阳
1 年前
看、未来

C++编程经验(7):delete之后到底要不要置空?

前往小程序,Get 更优 阅读体验!
立即前往
腾讯云
开发者社区
文档 建议反馈 控制台
首页
学习
活动
专区
工具
TVP
最新优惠活动
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP 最新优惠活动
返回腾讯云官网
看、未来
首页
学习
活动
专区
工具
TVP 最新优惠活动
返回腾讯云官网
社区首页 > 专栏 > C++编程经验(7):delete之后到底要不要置空?

C++编程经验(7):delete之后到底要不要置空?

作者头像
看、未来
发布 于 2021-09-18 11:44:28
2K 0
发布 于 2021-09-18 11:44:28
举报
文章被收录于专栏: CSDN搜“看,未来” CSDN搜“看,未来”

说来惭愧,是因为我忘了到底要怎么正确的delete,然后查到了这个话题,然后见识了一场大佬们的讨论。

辩题:C++ 里 delete 指针两次会怎么样?(后来被扭曲为:C++ delete之后到底要不要置空) 正方:C++ delete之后当然要置空了 反方:C++ delete之后不应该置空掉


首先是置空派的选手上场: 一直以来都是这么写的,书上、老师都是这么说的。

接着是不置空派选手上场: 一派胡言! 很怕这种观念又成为了某种教条。

作者:丁冬 链接:https://www.zhihu.com/question/38998078/answer/79321819 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

举个例子:

代码语言: javascript
复制
~scoped_ptr() // never throws
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
    boost::sp_scalar_destructor_hook( px );
#endif
    boost::checked_delete( px );
}

这是boost::scoped_ptr的实现,checked_delete只是增加了对incomplete type的检查:template inline void checked_delete(T * x)

代码语言: javascript
复制
{
    // intentionally complex - simplification causes regressions
    typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
    (void) sizeof(type_must_be_complete);
    delete x;
}

可以看见boost::scoped_ptr根本没有对delete后的指针进行置空,如果boost::scoped_ptr真的把其持有的指针置空,反而可能掩盖类似这样的错误:

代码语言: javascript
复制
boost::scoped_ptr<MyClass> sp(new MyClass);
// some code
sp.~boost::scoped_ptr<MyClass>();
// by the end of the scope, sp counld be destructed again

按理说任何一个非trivial的有效对象被多次析构都应该是明显的逻辑错误,构造和析构必须是一一对应的。这样的错误也许一般用户很少遇到,因为显式调用析构函数往往都是库作者干的事,但这不代表这种奇怪的错误完全不会发生。很不幸的是,对于这种逻辑错误开发者往往没有特别好的手段可以规避掉,二次delete一个悬垂指针行为是未定义的,也就是说错误是有可能被隐藏的。但是如果boost::scoped_ptr帮你把px给置空了,结果只会更糟糕:这下错误铁定是被彻底隐藏了,根本别想找轻易到。没有置空的话好歹有一定概率会崩溃给你看呢。当然“delete后置空指针”这种教条能流传这么久,肯定是有它的道理的。

关于到底什么时候需要置空指针,关键之处在于搞清楚置空指针到底解决了什么问题。 先来理一下nullptr和野指针/悬垂指针的区别:

代码语言: javascript
复制
解引用:
nullptr:未定义
野指针/悬垂指针:未定义
delete nullptr:良好定义,delete什么也不用做
野指针/悬垂指针:未定义
值:nullptr:明确
野指针/悬垂指针:未定义,无法确定

可以发现nullptr最大的优势在于值是明确的,也就是说分辨一个指针是不是nullptr比分辨一个指针是不是野指针/悬垂指针要容易得多。那delete后置空指针的最大价值就在于明确资源当前状态。你想判断一个资源是否有效时,你当然没法直接跑去看这个资源在不在,而是得询问资源的持有者是否仍然持有这个资源。如果所有被delete的指针都被置为nullptr,以后再去访问这个指针的时候,我们可以通过其与nullptr的比较轻松判断出资源是否已经被delete。当然,这个优势基于一个重要的前提:在设计上允许在资源已经失效的情况下,资源的持有者保持有效。如果资源的持有者也被干掉了,那即使你想通过nullptr判断资源是否存在,你也找不到持有资源的指针进行比较。至此,我们至少可以得出一个结论,如果对象是和持有其的指针一同销毁的,那置空指针就是脱裤子放屁。这个结论还可以引申一下:如果资源与其所有的持有者(含弱引用)一同被销毁,那即将消亡的持有者们都没有必要,也没有能力为资源的后续状态负责。/********************************/其实delete/free后置空这样的教条已经几乎走上了和goto-label之流一样的道路,很多人看到了前辈们留下的经验之谈,妄图死记住口口相传的best-practice,却忘记了前因后果。


接下来插入一则消息,中立派登场:

试一试怎么了,死的是程序,又不会是系统、电脑、或开发者。以后路还长着,连这个最简单最基本的都不敢试的话,以后会遇到更多麻烦。另一方面,你不能通过一次试的结果得出结论。因为那只能说明在特定编译器、特定crt下的结果。原理上你得知道delete是不改变指针值的。所以第二次delete的时候,行为未定义,什么事情都可能发生。好习惯永远是delete之后立刻赋nullptr。这样即便意外第二次delete了,也没关系,因为delete nullptr是有良好定义的。

作者:叛逆者 链接:https://www.zhihu.com/question/38998078/answer/79188320 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

我就是中立派的,我也去试了一下,结果我的VS给搞崩了,,,, 然后我又换到g++去试,最后发现:

请添加图片描述
请添加图片描述

该报错的它就是要报错,拦不住的。 就算二次置空了又怎么样?

这里我要讲一下,delete回收的是指针指向的那块内存,而上面的p、q指向的是同一块内存。


接下来又来了个正方观点,我觉得他这个观点挺乌龙的,因为我上面那个代码就是受他的启发:

作者:二律背反 链接:https://www.zhihu.com/question/38998078/answer/79157526 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Why doesn’t delete zero out its operand?Consider

代码语言: javascript
复制
delete p;
// ...
delete p;

If the … part doesn’t touch p then the second “delete p;” is a serious error that a C++ implementation cannot effectively protect itself against (without unusual precautions). Since deleting a zero pointer is harmless by definition, a simple solution would be for “delete p;” to do a “p=0;” after it has done whatever else is required. However, C++ doesn’t guarantee that.One reason is that the operand of delete need not be an lvalue. Consider:

代码语言: javascript
复制
delete p+1;
delete f(x);

Here, the implementation of delete does not have a pointer to which it can assign zero. These examples may be rare, but they do imply that it is not possible to guarantee that any pointer to a deleted object is 0.'' A simpler way of bypassing that rule’’ is to have two pointers to an object:

代码语言: javascript
复制
 T* p = new T;
	T* q = p;
	delete p;
	delete q;	// ouch!

C++ explicitly allows an implementation of delete to zero out an lvalue operand, and I had hoped that implementations would do that, but that idea doesn’t seem to have become popular with implementers.If you consider zeroing out pointers important, consider using a destroy function: template inline void destroy(T*& p) { delete p; p = 0; } Consider this yet-another reason to minimize explicit use of new and delete by relying on standard library containers, handles, etc.Note that passing the pointer as a reference (to allow the pointer to be zero’d out) has the added benefit of preventing destroy() from being called for an rvalue:

代码语言: javascript
复制
   int* f();
	int* p;
	// ...
	destroy(f());	// error: trying to pass an rvalue by non-const reference
	destroy(p+1);	// error: trying to pass an rvalue by non-const reference

======================= c++ primer 4th edition ====================== 5.11.6:After deleting a pointer, the pointer becomes what is referred to as a dangling pointer . A dangling pointer is one that refers to memory that once held an object but does so no longer. A dangling pointer can be the source of program errors that are difficult to detect. Setting the pointer to 0 after the object it refers to has been deleted makes it clear that the pointer points to no object.

我现在实在是不知道他到底要表达什么意思了。。。


好,反方大佬出场:

delete 之后赋值 nullptr 绝对是坏习惯,会掩盖真实的错误。也不利于使用各种 memory checker 工具找出错误。类似的还有为了防止 double free 而在 free 之后赋值 NULL,一样是错误的。在 C++ 里,任何资源释放的操作都应该在析构函数里进行,这样只要管好对象生命期就不会有资源泄漏了。

作者:陈硕

 
推荐文章
热心的砖头  ·  没有指针,Python如何实现链表、二叉树这些数据结构?_python没有指针是怎么确定地址的
2 月前
正直的键盘  ·  Python 有指针吗?_python中有没有指针
2 月前
非常酷的红茶  ·  Python3中的“指针” - DECHIN
2 月前
有胆有识的斑马  ·  Python seek()和tell()函数详解 - C语言中文网
2 月前
深情的楼房  ·  xampp本机访问解析php超级慢的原因之一_xampp网速很慢-CSDN博客
1 年前
温柔的野马  ·  token 过期后,如何自动续期?-腾讯云开发者社区-腾讯云
1 年前
重情义的西红柿  ·  如何启用日志以查看在Tomcat中为什么spring-data-jpa速度很慢?
1 年前
独立的柚子  ·  学习ASP.NET Core Blazor编程系列二十五——登录(4) - DotNet菜园 - 博客园
2 年前
大气的香蕉  ·  jupyter notebook无法导入第三方库问题_jupyter 中不能输入 ros2库_追逐繁星的girl的博客-CSDN博客
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号