C++编程经验(7):delete之后到底要不要置空?
说来惭愧,是因为我忘了到底要怎么正确的delete,然后查到了这个话题,然后见识了一场大佬们的讨论。
辩题:C++ 里 delete 指针两次会怎么样?(后来被扭曲为:C++ delete之后到底要不要置空) 正方:C++ delete之后当然要置空了 反方:C++ delete之后不应该置空掉
首先是置空派的选手上场: 一直以来都是这么写的,书上、老师都是这么说的。
接着是不置空派选手上场: 一派胡言! 很怕这种观念又成为了某种教条。
作者:丁冬 链接:https://www.zhihu.com/question/38998078/answer/79321819 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
举个例子:
~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)
{
// 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真的把其持有的指针置空,反而可能掩盖类似这样的错误:
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和野指针/悬垂指针的区别:
解引用:
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
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:
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:
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:
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++ 里,任何资源释放的操作都应该在析构函数里进行,这样只要管好对象生命期就不会有资源泄漏了。
作者:陈硕