【问题标题】:Is there any reason to check for a NULL pointer before deleting?是否有任何理由在删除之前检查 NULL 指针?
【发布时间】:2010-10-11 13:11:58
【问题描述】:

我经常看到在删除指针之前检查 NULL 的遗留代码,类似于,

if (NULL != pSomeObject) 
{
    delete pSomeObject;
    pSomeObject = NULL;
}

是否有任何理由在删除 NULL 指针之前对其进行检查?之后设置指针指向NULL的原因是什么?

【问题讨论】:

  • 不知道为什么他们在删除之前检查 NULL,因为删除 NULL 指针是完全安全的,但他们将其设置为 NULL 后缀的原因是,如果对象第二次被意外删除,你的程序不会爆炸(删除一个悬空指针是不好的)。

标签: c++ pointers null delete-operator


【解决方案1】:

删除空指针是完全“安全”的;它实际上相当于无操作。

您可能希望在删除之前检查 null 的原因是尝试删除 null 指针可能表明您的程序中存在错误。

编辑

注意:如果重载删除操作符,它可能不再对delete NULL“安全”

【讨论】:

  • @Dbger:我不确定你的意思,所以让我澄清一下我的帖子中的 I 是什么意思。如果您将 NULL 指针传递给删除机制,是的,在幕后,会进行 null 检查,如果指针为 null,则“函数”只是返回而不实际执行任何操作。但是您可能希望在删除之前检查空指针的 原因 是,如果您尝试删除已设置为 null 的指针,这意味着您对指针的假设不正确.如果您的假设不正确,那么您很可能有错误。
  • @Dbger:我们开始... 使用 ISO/IEC 14882:2003 第 5.3.5 节第 2 段,第二句:“在任一替代方案中,如果 delete 的操作数的值为操作无效的空指针。”
  • @Dbger:你是从那个谷歌代码页得到的吗?我也是。我太便宜了,不能在 ISO 网站上花 30 美元。像这样的废话正是当今世界的问题,IMO。
  • -1 请澄清您所说的“您可能希望在删除之前检查 null 的原因是尝试删除空指针可能表明您的程序中存在错误。”如问题中的代码所示,我看不出检查 null 将如何帮助检测/避免/解决此类错误,那么它有什么帮助?
  • @Randolpho 谢谢,这是有道理的。关键部分是“并记录您尝试删除 NULL 指针的事实”,这不在代码示例中,我不相信您在前面的答案或后续 cmets 中提到过它(尽管它可能在那里并且我我错过了,奇怪的事情发生了)。如果您在答案中添加此说明,将会很有帮助;我真的不知道你是什么意思。
【解决方案2】:

C++ 标准保证在 delete-expression 中使用空指针是合法的(第 8.5.2.5/2 节)。但是,未指定这是否会调用释放函数(operator deleteoperator delete[];§8.5.2.5/7,注意)。

如果使用空指针调用默认释放函数(即由标准库提供),则调用无效(第 6.6.4.4.2/3 节)。

但是,如果标准库不提供释放函数会发生什么,即当我们重载operator delete(或operator delete[])时会发生什么,目前尚不清楚。

一个称职的程序员会在释放函数内部处理空指针,而不是在调用之前,如 OP 的代码所示。同样,将指针设置为 nullptr/NULL删除仅用于非常有限的目的。有些人喜欢本着defensive programming 的精神这样做:它会使程序行为在出现错误的情况下稍微更可预测:删除后访问指针将导致空指针访问,而不是对随机内存位置的访问.尽管这两个操作都是未定义的行为,但在实践中空指针访问的行为更容易预测(它通常会导致直接崩溃而不是内存损坏)。由于内存损坏特别难以调试,因此重置已删除的指针有助于调试。

——当然,这是治疗症状而不是治疗原因(即错误)。 您应该将重置指针视为代码异味。干净、现代的 C++ 代码将明确内存所有权并进行静态检查(通过使用智能指针或等效机制),因此可证明避免这种情况。

奖励:重载operator delete的解释:

operator delete 是(尽管它的名字)一个可以像任何其他函数一样被重载的函数。每次使用匹配的参数调用 operator delete 时,都会在内部调用此函数。 operator new 也是如此。

重载operator new(然后还有operator delete)在您想要精确控制内存分配方式的某些情况下是有意义的。这样做甚至不是很困难,但必须采取一些预防措施以确保正确的行为。 Scott Meyers 非常详细地描述了这一点Effective C++

现在,假设我们要重载operator new 的全局版本以进行调试。在我们这样做之前,请注意以下代码中发生的事情:

klass* pobj = new klass;
// … use pobj.
delete pobj;

这里实际发生了什么?那么上面可以大致翻译成如下代码:

// 1st step: allocate memory
klass* pobj = static_cast<klass*>(operator new(sizeof(klass)));
// 2nd step: construct object in that memory, using placement new:
new (pobj) klass();

// … use pobj.

// 3rd step: call destructor on pobj:
pobj->~klass();
// 4th step: free memory
operator delete(pobj);

注意第 2 步,我们调用 new 的语法有点奇怪。这是对所谓的 placement new 的调用,它接受一个地址并在该地址构造一个对象。该运算符也可以重载。在这种情况下,它只是用来调用类klass的构造函数。

现在,不用多说,这里是运算符重载版本的代码:

void* operator new(size_t size) {
    // See Effective C++, Item 8 for an explanation.
    if (size == 0)
        size = 1;

    cerr << "Allocating " << size << " bytes of memory:";

    while (true) {
        void* ret = custom_malloc(size);

        if (ret != 0) {
            cerr << " @ " << ret << endl;
            return ret;
        }

        // Retrieve and call new handler, if available.
        new_handler handler = set_new_handler(0);
        set_new_handler(handler);

        if (handler == 0)
            throw bad_alloc();
        else
            (*handler)();
    }
}

void operator delete(void* p) {
    cerr << "Freeing pointer @ " << p << "." << endl;
    custom_free(p);
}

此代码只是在内部使用malloc/free 的自定义实现,大多数实现也是如此。它还创建一个调试输出。考虑以下代码:

int main() {
    int* pi = new int(42);
    cout << *pi << endl;
    delete pi;
}

它产生了以下输出:

Allocating 4 bytes of memory: @ 0x100160
42
Freeing pointer @ 0x100160.

现在,这段代码做了一些与operator delete 的标准实现完全不同的事情:它没有测试空指针!编译器不检查这个,所以上面的代码编译但它当您尝试删除空指针时,可能会在运行时产生令人讨厌的错误。

但是,正如我之前所说,这种行为实际上是出乎意料的,库编写者应该注意检查operator delete 中的空指针。这个版本有很大的改进:

void operator delete(void* p) {
    if (p == 0) return;
    cerr << "Freeing pointer @ " << p << "." << endl;
    free(p);
}

总之,虽然 operator delete 的草率实现可能需要在客户端代码中进行显式空检查,但这是非标准行为,只能在旧版支持中容忍(如果有的话) .

【讨论】:

  • 第一点没看懂,能否详细说明重载
  • rajKumar:见更新。我希望这有帮助。理解这些重载并不容易。我只能建议阅读 Scott Meyers 的 Effective C++(也是一般性的;它是事实上的 C++ 操作手册)。
  • 将已删除的指针设置为 NULL 有另一个好处:保证任何后续尝试取消引用指针都会立即崩溃,而不是做谁知道什么。
  • 您应该知道 free(NULL) 也是一个无操作,因此您的示例并不是真正的问题示例。除此之外,击中头部的标记。
  • 由于重载运算符损坏而在删除前检查 null 是错误的 - 修复运算符!
【解决方案3】:

删除 null 是无操作的。在调用 delete 之前没有理由检查 null。

如果指针为空包含一些您关心的附加信息,您可能需要检查是否为空。

【讨论】:

  • -1 表示“没有理由”只是因为你想不出一个。性能是一个可能的原因。
  • +1 来对抗那个荒谬的 -1(我希望我能对 cme​​ts 投反对票)。 确实没有理由检查 NULL,而性能是这样做的一个原因...在删除之前检查 NULL 是多余的,它的冗余是编译器无法消除。这个答案有用地包括观察到测试对于if (p != NULL) {dosomethingwith(p); delete p; } 不是多余的,这是一种常见的模式。
【解决方案4】:

在内部删除对 NULL 的检查。你的测试是多余的

【讨论】:

  • Delete.cpp 调用 Free.c,它检查是否为 NULL,然后返回。 QED。 void __cdecl _free_base (void * pBlock) { PHEADER pHeader; if (pBlock == NULL) 返回;
  • -1 是的,NULL 的测试在逻辑上是多余的,但这并不能回答为什么它可能有用的问题
【解决方案5】:

根据 C++03 5.3.5/2,删除空指针是安全的。 以下内容引用自标准:

在任一选择中,如果 delete 的操作数的值是 空指针操作无效。

【讨论】:

  • -1 是的,它是安全的,但这并不能回答为什么无论如何进行测试可能有用的问题。
【解决方案6】:

如果 pSomeObject 为 NULL,delete 不会做任何事情。所以不,你不必检查 NULL。

如果某些笨蛋有可能尝试使用指针,我们认为在删除指针后将其分配为 NULL 是一种很好的做法。使用 NULL 指针比使用不知道是什么的指针稍微好一点(NULL 指针会导致崩溃,指向已删除内存的指针可能不会)

【讨论】:

  • 不仅使用指针,而且指关节可能会再次尝试删除它。不允许双重删除。
  • 将已删除的指针设置为NULL 也可能隐藏严重的问题,如果周围的代码非常具有防御性:在取消引用之前检查所有指针是否与NULL 相对应。
【解决方案7】:

没有理由在删除之前检查 NULL。 如果在代码中的某处通过执行 NULL 检查是否已经分配了某个对象,则可能需要在删除后分配 NULL。一个示例是按需分配的某种缓存数据。每当您清除缓存对象时,您将 NULL 分配给指针,以便分配对象的代码知道它需要执行分配。

【讨论】:

  • -1 表示“没有理由”只是因为你想不出一个。性能是一个可能的原因。
  • +1 因为this again
【解决方案8】:

我相信之前的开发人员对其进行了“冗余”编码以节省一些毫秒: 在删除对象时将指针设置为 NULL 是一件好事,因此您可以在删除对象后立即使用如下行:

if(pSomeObject1!=NULL) pSomeObject1=NULL;

但无论如何,delete 都会进行精确的比较(如果它为 NULL,则什么也不做)。为什么要这样做两次?在调用 delete 之后,您始终可以将 pSomeObject 分配给 NULL,而不管它的当前值是什么 - 但如果它已经具有该值,这将有点多余。

所以我敢打赌,这些行的作者试图确保 pSomeObject1 在被删除后始终为 NULL,而不会产生可能不必要的测试和分配的成本。

【讨论】:

  • 如果操作员删除仍然要这样做,它如何节省时间?在大多数情况下,由于反复检查,它肯定会多使用 50% 的时间吗?
  • 我猜想是为了加快指针确实为NULL的情况。想象一下它不进行比较:无论如何,delete 都会与 NULL 进行比较,立即退出,然后再次(不必要地)将指针分配给 NULL。由于函数调用比与常量进行比较更昂贵 - 通过首先进行比较(无论如何 delete 都会这样做),您可以节省一些毫秒来调用删除代码调用加上其他毫秒的(那时不必要的)NULL 分配。
  • 对此+1,因为它是迄今为止指出测试的潜在有效原因的唯一答案,假设删除已正确实施。
  • @JimBalter 调用函数不是免费的 - 推入参数,远程调用,推出参数,然后推入返回值,返回,推出 ret 值。除非编译器可以将其内联(我承认现在大多数都使用“删除”),否则仅调用“免费”将意味着进行可怕的比较 plus 一个函数调用,plus将 NULL 分配给 已经 在其值中包含 NULL 的变量 - 浪费了太多毫秒。
【解决方案9】:

这取决于你在做什么。例如,free 的一些较旧的实现在传递NULL 指针时会不高兴。一些图书馆仍然有这个问题。例如,Xlib 库中的XFree 表示:

描述

XFree 函数是一个 通用 Xlib 例程 释放指定的数据。你必须 用它来释放之前的任何对象 由 Xlib 分配,除非有备用 函数被明确指定为 物体。 NULL 指针不能 传递给这个函数。

所以考虑释放NULL 指针作为一个错误,你会很安全。

【讨论】:

  • -1 这个问题是关于删除,而不是免费或 XFree。 free 至少从 UNIX V7 开始就检查 NULL,并且语言标准要求这样做,就像 delete 一样。
【解决方案10】:

就我的观察而言,使用 delete 删除空指针在基于 unix 的机器(如 PARISC 和 itanium)中是安全的。但是对于 Linux 系统来说是非常不安全的,因为那时进程会崩溃。

【讨论】:

  • 在 Linux 上不安全不是真的;标准保证它是安全的,我已经做过无数次了。
  • 如果你的进程崩溃了,你的编译器坏了,我希望你现在有一个新的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-24
  • 2011-09-05
  • 2013-08-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多