前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++编程经验(7):delete之后到底要不要置空?

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

作者头像
看、未来
发布2021-09-18 11:44:28
2.4K0
发布2021-09-18 11:44:28
举报
文章被收录于专栏: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 thatrule’’ 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++ 里,任何资源释放的操作都应该在析构函数里进行,这样只要管好对象生命期就不会有资源泄漏了。

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

是吧,大佬都这么说了。


又有反方大佬出场:

delete 后置为 nullptr 在 C++ 中一般是不必要的,因为 C++ 可以用 RAII 来管理内存,而析构函数是被编译器保证只执行一次的。 不过 free 后置为 NULL 在 C 中似乎常常是不可避免的,因为需要用 NULL 来判断状态,不然代码就会写得非常复杂。


最后呢,我的观点: 关于到底什么时候需要置空指针,关键之处在于搞清楚置空指针到底解决了什么问题。 如果没有必要,那就,放着呗,真错了,错了就是错了,早点报出来早点解决掉。

此外,智能指针真的要去尝试使用一下了。

精彩不容错过:https://www.zhihu.com/question/38998078

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/08/09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档