我首先要说的是,使用智能指针,你永远不必担心这一点。
以下代码有什么问题?
Foo * p = new Foo;
// (use p)
delete p;
p = NULL;
an answer and comments引发了另一个问题。来自Neil Butterworth的一条评论产生了一些赞成票:
在删除后将指针设置为NULL不是C ++中的通用优良做法。有时候做一件好事,有时候没有意义,可以隐藏错误。
有很多情况下它无济于事。但根据我的经验,它不会伤害。有人开导我。
答案 0 :(得分:78)
将指针设置为0(在标准C ++中为“null”,来自C的NULL定义稍有不同)避免双重删除时崩溃。
请考虑以下事项:
Foo* foo = 0; // Sets the pointer to 0 (C++ NULL)
delete foo; // Won't do anything
鉴于:
Foo* foo = new Foo();
delete foo; // Deletes the object
delete foo; // Undefined behavior
换句话说,如果你没有将删除的指针设置为0,那么如果你正在进行双重删除,你将遇到麻烦。反对在删除后将指针设置为0的参数是这样做只是掩盖双重删除错误并使它们未处理。
最好不要有双删除错误,显然,但根据所有权语义和对象生命周期,这在实践中很难实现。我更喜欢UB上的蒙面双删除错误。
最后,关于管理对象分配的旁注,我建议你看一下std::unique_ptr
的严格/单一所有权,std::shared_ptr
用于共享所有权,或者另一个智能指针实现,这取决于你的需求。
答案 1 :(得分:53)
在删除指向的指针之后将指针设置为NULL肯定不会受到伤害,但它通常对一个更基本的问题有点助手:为什么你首先使用指针?我可以看到两个典型的原因:
std::vector
的工作方式,它解决了意外将指针留给释放的内存的问题。没有指针。new
返回的指针可能与调用delete
的指针不同。在此期间,多个对象可能同时使用了该对象。在这种情况下,最好使用共享指针或类似的东西。我的经验法则是,如果你在用户代码中留下指针,你就是在做错了。指针不应该首先指向垃圾。为什么没有一个对象负责确保其有效性?为什么它的范围在指向对象时不会结束?
答案 2 :(得分:42)
我有一个更好的最佳实践:在可能的情况下,结束变量的范围!
{
Foo* pFoo = new Foo;
// use pFoo
delete pFoo;
}
答案 3 :(得分:28)
在删除指向的对象后,我总是设置指向NULL
(现在为nullptr
)的指针。
它可以帮助捕获许多对释放内存的引用(假设你的平台在空指针的deref上出错)。
例如,如果你有指针的副本,它将不会捕获所有对freed内存的引用。但有些人比没有人好。
它会屏蔽双重删除,但我发现它们远比访问已释放的内存少得多。
在许多情况下,编译器会优化它。因此,不必要的说法并不能说服我。
如果您已经在使用RAII,那么您的代码中的delete
开头并不多,因此额外分配导致混乱的论点并不能说服我。
调试时,通常很方便查看空值而不是陈旧的指针。
如果这仍然困扰你,请使用智能指针或参考。
当资源被释放时,我还将其他类型的资源句柄设置为无资源值(通常只在为封装资源而编写的RAII包装器的析构函数中)。
我参与了一项大型(900万份报表)商业产品(主要是C)。有一次,我们使用宏魔法在释放内存时使指针无效。这立即暴露了很多潜伏的错误,并迅速修复。据我所知,我们从未有过双重免费的错误。
更新:Microsoft认为这是一种安全的良好做法,并建议在其SDL政策中采用这种做法。显然,如果使用/ SDL选项进行编译,MSVC ++ 11将stomp the deleted pointer自动(在许多情况下)。
答案 4 :(得分:12)
首先,关于这个和密切相关的主题存在很多现有问题,例如Why doesn't delete set the pointer to NULL?。
在您的代码中,问题是(使用p)。例如,如果某个地方有这样的代码:
Foo * p2 = p;
然后将p设置为NULL可以完成很少,因为您仍然需要担心指针p2。
这并不是说将指针设置为NULL总是毫无意义的。例如,如果p是指向资源的成员变量,该资源的生命周期与包含p的类不完全相同,那么将p设置为NULL可能是指示资源是否存在的有用方法。
答案 5 :(得分:7)
如果delete
后面有更多代码,请执行。在构造函数中或在方法或函数末尾删除指针时,编号
这个比喻的目的是在运行期间提醒程序员该对象已被删除。
更好的做法是使用自动删除目标对象的智能指针(共享或范围)。
答案 6 :(得分:3)
正如其他人所说,delete ptr; ptr = 0;
不会导致恶魔飞出你的鼻子。但是,它确实鼓励使用ptr
作为各种标志。代码变得充满delete
并将指针设置为NULL
。下一步是通过代码分散if (arg == NULL) return;
以防止意外使用NULL
指针。一旦针对NULL
的检查成为检查对象或程序状态的主要方法,就会出现问题。
我确信在某个地方使用指针作为标志有一种代码味道,但我还没找到。
答案 7 :(得分:2)
在具有适当错误检查的结构良好的程序中,没有理由不将其赋值为null。在这种情况下,0
是一个普遍认可的无效价值。努力工作并尽快失败。
许多反对分配0
的论点都暗示可以隐藏错误或使控制流复杂化。从根本上说,这可能是一个上游错误(不是你的错(抱怨不好的双关语))或代表程序员的另一个错误 - 甚至可能表明程序流程变得过于复杂。
如果程序员想要引入一个可能为null的指针作为特殊值并写下所有必要的躲避,那就是他们故意引入的并发症。检疫越好,越早发现滥用情况,就越不能传播到其他计划中。
可以使用C ++功能设计结构良好的程序以避免这些情况。您可以使用引用,或者您可以只说“传递/使用null或无效参数是一个错误” - 这种方法同样适用于容器,例如智能指针。增加一致和正确的行为可以阻止这些错误的发生。
从那里开始,你只有一个非常有限的范围和上下文,其中可能存在(或允许)空指针。
同样可以应用于非const
的指针。跟随指针的值是微不足道的,因为它的范围很小,并且检查和定义了不正确的使用。如果您的工具集和工程师在快速阅读后无法遵循该程序,或者存在不适当的错误检查或不一致/宽松的程序流程,那么您还有其他更大的问题。
最后,当您想要引入错误(涂鸦),检测对释放的内存的访问以及捕获其他相关的UB时,您的编译器和环境可能会有一些防范。您也可以在程序中引入类似的诊断,通常不会影响现有程序。
答案 8 :(得分:2)
删除后显式归零强烈建议读者指示某些概念上是可选的指针。如果我看到这样做了,我会开始担心源中的所有指针都被使用,它应该首先针对NULL进行测试。
如果这就是你的意思,最好在源代码中使用boost::optional
之类的内容optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();
但如果你真的希望人们知道指针“变坏了”,我会与那些说最好的事情就是让它超出范围的人达成100%的协议。然后,您正在使用编译器来防止在运行时出现错误解除引用的可能性。
这是所有C ++洗澡水中的婴儿,不应该扔掉它。 :)
答案 9 :(得分:2)
我会稍微改变你的问题:
你会使用未初始化的吗? 指针?你知道,一个你没有 设置为NULL或分配内存 指向?
有两种情况可以跳过将指针设置为NULL:
与此同时,争论将指针设置为NULL可能会隐藏错误,这听起来像是在争论你不应该修复错误,因为修复可能会隐藏另一个错误。如果指针未设置为NULL,可能显示的唯一错误是尝试使用指针的错误。但是将它设置为NULL实际上会导致完全相同的错误,如果你将它与释放的内存一起使用,不会吗?
答案 10 :(得分:2)
如果您没有其他约束强制您在删除它之后设置或不将指针设置为NULL(Neil Butterworth提到了一个这样的约束),那么我个人的偏好就是保留它。
对我来说,问题不是“这是个好主意吗?”但是“通过这样做我会阻止或允许成功的行为是什么?”例如,如果这允许其他代码看到指针不再可用,为什么其他代码甚至在释放后尝试查看释放的指针?通常,这是一个错误。
它还做了比必要工作更多的工作以及阻碍事后调试。你不需要它后触摸内存越少,就越容易找出崩溃的原因。很多时候,我依赖的事实是,内存处于与诊断和修复错误的特定错误时类似的状态。
答案 11 :(得分:1)
“有时候这是好事,有时候没有意义,可以隐藏错误”
我可以看到两个问题: 那个简单的代码:
delete myObj;
myobj = 0
成为多线程环境中的for-liner:
lock(myObjMutex);
delete myObj;
myobj = 0
unlock(myObjMutex);
Don Neufeld的“最佳实践”并不总是适用。例如。在一个汽车项目中,即使在析构函数中,我们也必须将指针设置为0。我可以想象在安全关键软件中这样的规则并不少见。遵循它们比试图说服更容易(也更明智) 代码中每个指针使用的团队/代码检查器,使该指针归零的行是多余的。
另一个危险是在使用代码的异常中依赖这种技术:
try{
delete myObj; //exception in destructor
myObj=0
}
catch
{
//myObj=0; <- possibly resource-leak
}
if (myObj)
// use myObj <--undefined behaviour
在这样的代码中,要么产生资源泄漏并推迟问题,要么进程崩溃。
所以,这两个问题在我脑海中自发地发生(Herb Sutter肯定会说得更多)为我提出了“如何避免使用智能指针,并使用普通指针安全地完成工作”的所有问题都已过时
答案 12 :(得分:1)
是
它可以做的唯一“伤害”是在你的程序中引入低效率(一种不必要的存储操作) - 但是在大多数情况下,这种开销与分配和释放内存块的成本相比是微不足道的。
如果你不这样做,你将一天有一些讨厌的指针derefernce错误。
我总是使用宏来删除:
#define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; }
(类似于数组,free(),释放句柄)
您还可以编写“self delete”方法来引用调用代码的指针,这样它们就会强制调用代码的指针为NULL。例如,要删除许多对象的子树:
static void TreeItem::DeleteSubtree(TreeItem *&rootObject)
{
if (rootObject == NULL)
return;
rootObject->UnlinkFromParent();
for (int i = 0; i < numChildren)
DeleteSubtree(rootObject->child[i]);
delete rootObject;
rootObject = NULL;
}
修改强>
是的,这些技术确实违反了关于使用宏的一些规则(是的,现在你可能会用模板获得相同的结果) - 但是通过使用多年我永远不会访问死亡内存 - 调试您可能面临的问题的最恶劣,最困难和最耗时的事情之一。在多年的实践中,他们已经有效地消除了我介绍过的每个团队中的一类错误。
还有很多方法可以实现上面的内容 - 我只是想说明如果他们删除了一个对象,强制人们将NULL指针为NULL,而不是为他们提供释放不为NULL的内存的方法来电者的指针。
当然,上面的例子只是迈向自动指针的一步。我没有建议,因为OP专门询问不使用自动指针的情况。
答案 13 :(得分:1)
让我展开你已经提到的问题。
以下是您用问题点形式提出的问题:
在删除后将指针设置为NULL不是C ++中的通用优良做法。有时候:
然而,当坏时,没有时间!您将不通过显式归零来引入更多错误,您将不会泄漏内存,您将不会导致未定义的行为发生。
所以,如果有疑问,请将其归零。
话虽如此,如果你觉得你必须显式地忽略一些指针,那么对我来说这听起来好像你没有足够的分割方法,应该看一下名为“Extract method”的重构方法来拆分将方法分成不同的部分。
答案 14 :(得分:0)
如果要在再次使用之前重新分配指针(取消引用它,将其传递给函数等),使指针为NULL只是一个额外的操作。但是,如果您不确定它是否会在重新使用之前重新分配,则将其设置为NULL是一个好主意。
正如许多人所说,使用智能指针当然要容易得多。
编辑:正如Thomas Matthews在this earlier answer中所说的,如果在析构函数中删除指针,则不需要为它分配NULL,因为它不会被再次使用,因为该对象正在被销毁已经
答案 15 :(得分:0)
我可以想象在删除它之后将指针设置为NULL在极少数情况下是有用的,在这种情况下存在在单个函数(或对象)中重用它的合法场景。否则它没有意义 - 指针需要指向有意义的东西,只要它存在 - 句号。
答案 16 :(得分:0)
如果代码不属于应用程序中性能最关键的部分,请保持简单并使用shared_ptr:
shared_ptr<Foo> p(new Foo);
//No more need to call delete
它执行引用计数并且是线程安全的。您可以在tr1(std :: tr1命名空间,#include&lt; memory&gt;)中找到它,或者如果您的编译器没有提供它,请从boost获取它。
答案 17 :(得分:0)
总有Dangling Pointers担心。