删除NULL指针是否安全?

qiu*_*fei 281 c++ pointers memory-management null-pointer delete-operator

删除NULL指针是否安全?

它是一种很好的编码风格吗?

rus*_*lik 249

delete无论如何都要执行检查,因此在您身边进行检查会增加开销并且看起来更加丑陋.一个非常好的做法后,指针设置为NULL delete(有助于避免双重缺失和其它类似的内存损坏问题).

我也很喜欢,如果delete默认情况下将参数设置为NULL,就像在

#define my_delete(x) {delete x; x = NULL;}
Run Code Online (Sandbox Code Playgroud)

(我知道R和L值,但不是很好吗?)

  • 一个非常**的上帝练习**不是**删除后将指针设置为NULL.删除它伪装内存分配错误,这是一个非常糟糕的事情后,指针设置为NULL.一种程序,它是正确的不会删除一个指针两次,而不会删除指针两次****应该崩溃的程序. (131认同)
  • 请注意,即使在删除时将其设置为NULL,仍可能有其他几个指向同一对象的指针. (70认同)
  • @Damon然而,尽管你的严厉所有权规则被废除,但无锁结构比基于锁的结构更可靠.是的,我的同事确实爱我这些结构提供的增强的执行配置文件以及他们维护的严格的线程安全性,这使得更容易推理代码(非常适合维护).但是,这一点或您隐含的人身攻击都与正确性,有效性或所有权的任何定义无关.你提出的建议是一个很好的经验法则,但它不是普遍的法律,也不是标准的规定. (26认同)
  • @Alice:标准在这方面说的是无关紧要的.标准定义删除空指针30年前因某些荒谬的原因有效,因此它是合法的(很可能是C遗留).但删除相同的指针两次(即使在更改其位模式后)仍然是一个严重的程序错误.不是通过标准的措辞,而是通过程序逻辑和所有权.正如删除空指针一样,由于空指针对应于_no object_,因此无法删除任何内容.程序必须**确切地知道对象是否有效以及谁拥有它,以及何时可以删除它. (14认同)
  • 在大多数情况下,在我的代码中,指针在删除后超出范围.比仅将其设置为NULL更安全. (12认同)
  • @Damon你强加自己的信念到代码的正确性面积:指针表示弱引用选项类型,用NULL表示没有价值.根据类型理论,删除选项类型也应该为可选部分定义; 否则它不是可选的.所以,你必须能够安全地删除NULL; 否则,你打破了类型合同,因此无法证明其正确性.所有权无关紧要:由于某种原因,指针很弱,如果这是你的用例,请使用shared_ptr.程序不需要完全了解所有权; 它只需要不破坏它的限制. (10认同)
  • @Damon我是几个无锁结构的维护者,并试图发表一篇关于一个新颖的无锁trie的论文.无锁结构的一个主要特征是线程可能需要帮助另一个线程,而不是自己进行.这通常需要几个能够删除的指针.事实上,对于仅使用CAS而非DCAS(大多数商用硬件)的硬件上的某些算法,这是可证实的.同样,在这种情况下删除可能发生的时间通常没有很好地定义,因为它们不会在每次执行的同时发生. (10认同)
  • @Damon删除NULL并不含糊不清; 它每次都做同样的事情.它定义明确; 规范存在并说出它的作用,并且它每次都做同样的事情.这是正确的; 程序的IO都与规范匹配,并且它与可选类型的类型理论规范相匹配.此外,对象有效性对这些中的任何一个都没有影响; 弱指针可能指向有效或无效的对象,但不是模糊,定义不正确或不正确.没有额外的工作,就不可能证明它们是否有效; 这是不可知的.是否有任何带有原始指针的C++程序不正确? (9认同)
  • @Damon这里的缺陷是你对所有权的严格看法,一般都不需要标准,类型理论或逻辑.您的每个"要求"都可以推翻而不会失去正确性; 多指针可能会删除,没有指针需要拥有,删除的时间不需要很好地定义,弱指针不需要知道它们指向的对象是无效的.但这些都与删除NULL合法或将指针设置为NULL以隐藏双重释放无关; 事实上,这样做可以提高正确性,因为这意味着程序不会出现错误或未定义的行为. (9认同)
  • @Damon首先,这不是真的; 该标准进行了多次更改,导致绝大多数代码中断.有关详细信息,请参阅STL的历史记录.其次,程序正确性不存在于真空中; 你必须根据规范证明程序是正确的.定义明确有一个规范:每次运行相同的值时,程序是否做同样的事情?最后,模糊性意味着两个同样有效的结果来自相同的值.正确性意味着定义明确,这意味着含糊不清.这些都不以任何方式处理知道对象是否有效. (8认同)
  • @Damon你说的只是假的.假设我有一个指向某个对象的原始指针,就像某个框架一样(这通常发生在QT,WT等).框架删除该对象,可能是为了响应我无法控制的代码.现在我有一个指向无效对象的指针,但我不知道它是无效的(实际上,无法知道,因为我链接的代码很多都超出了我的控制范围).此外,不存在拥有指针的概念; 所有指针都可以删除它们指向的对象,并且不需要任何指针; 允许堆在程序关闭时清理普通对象是完全有效的. (8认同)
  • @Damon删除设置为对象的指针,然后删除设置为NULL的指针的程序是正确的.这就是标准所说的. (7认同)
  • @Damon程序的正确性取决于程序行为是否与规范相关.该规范的一部分是用户相关的(代码是否符合我的要求),部分是与标准相关的(它是否遵循语言规则).在这两种情况下,释放NULL的程序都是正确的; 它遵循语言规则,不会导致错误.它不是"偶然"工作; 这已被讨论,并被包含在标准中,不仅仅是遗留的,而是因为它很有用,并且在它们毫无意义的区域中删除对NULL的检查.它适用于C++的设计. (7认同)
  • @ruslik:原始指针成员在现代C++中是一个非常糟糕的主意. (6认同)
  • @Alice:我祝你未来好运,因为有几个exectuion正在成为(或实际上已经是)常态,而不是例外.像"任何指针可以删除"或"删除的所有权和时间不需要明确定义"这样的想法将帮助您编写强大的程序,并在您的同事中赢得很多爱. (5认同)
  • @Alice:不同的是,这个不吉利的标准规则允许程序错误地行事(希望我拼写正确)以执行as-if correct.事实上,对象的有效性和所有权根本不明确(你可以说这是猜测).它只是偶然起作用,因为问题被忽略了.更好的方法是尽早崩溃(或抛出,如果你愿意),这迫使开发人员确保程序的逻辑是正确的.设置一个指向`(T*)1`而不是`nullptr`的指针会这样做,如果你再次尝试删除它会很难崩溃. (4认同)
  • JimBalter这是一个很好的观点,@ Damon似乎也没有意识到:约束有成本,而且由于所涉及的成本,C故意明确地避免了许多限制.删除NULL是完全合理的,如果它在没有安全性损失的情况下更快地制定一个慢速的所有权概念(在许多情况下证明是正确的).如果没有任何好处,为什么要使用较慢的机制来保证安全? (4认同)
  • 他们呢?它们通常是智能指针,或者它们是我自己的ad hoc RAII类的成员,就像我说的那样,当指针被删除时它超出范围 - 或者它们指向一个生命周期延伸超过指针的对象包含指针的对象. (3认同)
  • @ruslik:当包含对象超出范围时,类成员超出范围.因此,在dtor中,将它们设置为零是没有意义的.现在,在赋值运算符中,可能需要删除指向子对象的指针,但是只有当你已经有一个`new`子对象时!首先"删除"旧对象并不是例外 - 你最终可能没有任何子对象.因此,您不会将指针设置为NULL; 你将它设置为新对象. (3认同)
  • 如果重复使用过时指针总是会导致立即崩溃,那么我“可能”同意@Damon 的建议。但是内存会在可预测(堆栈)和不可预测(堆)位置中得到重用。非空指针在取消引用之前可能会被多次传递和复制,从而产生隐藏的别名和远离原始问题地点的各种风格的 Heisenbug。在我的职业生涯中,我不得不调试其中一些错误,因此我坚信尽快将过时的指针重置为 NULL,作为基本的卫生举措。 (3认同)
  • 我想知道"移动聊天"建议在这个评论帖中的位置... (3认同)
  • @sth肯定,但它总比没有好.在大多数情况下,只有一个指针. (2认同)
  • @Alice:你不明白。如果你有一个指针,这个指针要么未初始化/无效,要么指向一个对象(不拥有它),或者它拥有一个对象。无论如何,这是必须始终准确无误地知道的事情。只有一个实例,即拥有指针,它被允许(并且需要)在明确定义的时间删除对象。如果不是这种情况,则您的程序逻辑是错误的。删除合法的 NULL 并设置指向该值的无效指针隐藏了这个问题。它使那些_不_获得所有权的程序“表现良好”。 (2认同)
  • "我知道R和L值" - 但显然你不知道参考文献.`<template T> void my_delete(T*&p){delete p; p = NULL; }` (2认同)
  • @Alice"弱指针不需要知道它们指向的对象是无效的" - 如果可以删除它们就会这样做; 在这种情况下,他们需要引用计数以避免多次重新分配."但这一切都与删除NULL合法或将指针设置为NULL以隐藏双重释放"有关 - 更有可能在删除后将指针设置为NULL以将其返回到"重置"状态.这是一个非常有效的语义,并且在拥有指针之前就已经使用了......它具有Damon似乎没有意识到的内存成本.仍有机器具有有限的内存,这些事情很重要.:-) (2认同)
  • 注意:**使用**宏将导致[**双重评估**](http://stackoverflow.com/questions/39439181/what-is-double-evaluation-and-why-should-it-be-避免/39439236#39439236) 如果使用*表达式*。示例:`my_delete(Order3PizzasAndBurnTheBakery())` (2认同)

Chu*_*dad 74

来自C++ 0x草案标准.

$ 5.3.5/2 - "[...]在任何一种方法中,删除操作数的值可能是空指针值.[...'"

当然,没有人会对具有NULL值的指针进行"删除",但这样做是安全的.理想情况下,不应该有删除NULL指针的代码.但是当在循环中删除指针(例如在容器中)时,它有时很有用.由于删除NULL指针值是安全的,因此可以真正编写删除逻辑,而无需显式检查要删除的NULL操作数.

顺便说一下,C Standard $ 7.20.3.2也表示NULL指针上的'free'不执行任何操作.

free函数导致ptr指向的空间被释放,即可用于进一步分配.如果ptr是空指针,则不执行任何操作.

  • 如果它没有故意在非优化代码中引入低效率,我真的很喜欢这个答案的引用。正如接受的答案所述,删除空指针是无操作的。因此,在删除指针之前检查指针是否为空是完全无关紧要的。 (2认同)

Jon*_*ler 44

是的,这是安全的.

删除空指针没有坏处; 如果将未分配的指针初始化为零然后简单地删除,它通常会减少函数尾部的测试次数.


由于上一句话引起了混淆,因此所描述的一个例子 - 这不是例外安全的:

void somefunc(void)
{
    SomeType *pst = 0;
    AnotherType *pat = 0;

    …
    pst = new SomeType;
    …
    if (…)
    {
        pat = new AnotherType[10];
        …
    }
    if (…)
    {
        …code using pat sometimes…
    }

    delete[] pat;
    delete pst;
}
Run Code Online (Sandbox Code Playgroud)

可以使用示例代码选择各种类型的nits,但概念是(我希望)清楚.指针变量初始化为零,因此delete函数末尾的操作不需要测试它们在源代码中是否为非空; 无论如何,库代码执行该检查.


Bri*_*ndy 20

删除空指针无效.这不是一个好的编码风格,因为它不是必需的,但它也不错.

如果您正在寻找良好的编码实践,请考虑使用智能指针,这样您根本不需要delete.

  • 人们想要删除NULL指针的时间是当他们不确定它是否包含NULL时...如果他们知道它是NULL,那么他们就不会考虑删除并因此询问;-). (18认同)
  • 对于性能,可读性和可维护性,IMO冗余检查肯定是不好的. (3认同)

ash*_*mun 5

为了补充ruslik的答案,在C++ 14中你可以使用这种结构:

delete std::exchange(heapObject, nullptr);
Run Code Online (Sandbox Code Playgroud)