【问题标题】:Avoid program crash when deleting pointer删除指针时避免程序崩溃
【发布时间】:2014-07-16 00:01:51
【问题描述】:

以下代码中的第二次删除会导致程序崩溃,因为它之前已经被删除过:

int* i = new int;
delete i;
delete i;

尝试使用异常来捕获它也无济于事:

int* i = new int;
delete i;
try {
  delete i;
} catch(exception& e) { // program just crashes, doesn't go into this exception block
  cout << "delete failed" << endl;
}

如何进行安全删除(先检查指针指向的区域是否已经被删除)?

或者如果不可能的话,如何吐出发生crash的行号(不用调试工具)?

【问题讨论】:

  • 无法安全删除。
  • 你能做的最好的就是在删除后将 I 设置为 NULL。或者,只是不要双重删除! (使用 RAII,你根本不需要删除)。
  • 没有便携的方式。取决于架构、操作系统、分配库......在某些情况下,它甚至可能不会在第二次删除时崩溃,但要晚得多。在某些情况下,它可能会引发 SIGABORT……在其他情况下,它可能是 SIGSEGV。
  • @OliCharlesworth: nullptr 在 C++11 中
  • 或者定义你自己的重载删除操作符(比如调试构建),如果你可以接受这个开销(=检测和抛出,不要忽略)...

标签: c++


【解决方案1】:

对于 C++11 之前的编译器,您可以将要删除的指针设置为 (0, NULL) 或在 C++11 编译器之后设置为 nullptr。然而,这仅仅解决了问题(看下面的例子):

void deletePointer(int* & iptr) {
  delete iptr;
  iptr = nullptr;
}

没有可移植的标准测试来检查指针是否“有效”以进行删除。如果您的编译器支持 C++11,您能做的最好的事情就是使用智能指针。因此,您不必担心无效删除。

【讨论】:

    【解决方案2】:

    让我们首先说double delete is an undefined behaviour...,这意味着未定义的行为:)

    请允许我提醒一下deleting a null pointer is a no-op。所以不需要检查指针是否为空。

    如果没有更多上下文,答案是没有可移植的解决方案

    根据架构、操作系统,甚至你使用的内存分配库,它会产生不同的效果,并为你提供不同的检测选项。

    无法保证程序在第二次删除时会崩溃。它可能会简单地破坏内存分配器结构,这种方式只会在一段时间后崩溃。

    如果您的目标是检测问题,您最好的机会是设置程序以捕获崩溃信号(例如 SIGABRT、SIGSEGV、SIGBUS...)并在信号处理程序上打印堆栈跟踪,然后再允许程序终止/写入核心文件。正如我上面所说,这可能是可能不是内存损坏的地方......但它将是内存分配器/程序无法继续的地方没有了。

    那是侵入性较小的。

    使用自定义内存分配器或带有调试选项的内存分配器(例如 Solaris 中的 libumem)可以帮助您更早或更准确地检测问题所在。问题是,通常会有更大或更小的性能损失。

    如果您的目标是防止问题发生...那么您必须采用最佳实践...例如。

    1. 通常使用 RAII 或智能指针,或者至少在您无法在整个程序中安全地建立内存所有权时使用它们。
    2. 至少,请始终记住将已删除的指针设置为空。这并不能保证任何事情,因为您始终可以进行并发删除...但它有助于减少可能发生崩溃的情况。

    【讨论】:

    • 我不喜欢设置为空的建议。它只是隐藏了错误。我更赞成让它崩溃并修复双重删除。或者你的第二个建议。
    • @nwp 恢复了顺序以暗示对智能指针的偏好。尽管如此,我还是不喜欢将已删除的指针设置为 0 的建议……如果这有助于我避免在高峰时段发生崩溃而导致许多用户没有服务,我当然会这样做。有时您的代码库或环境不允许您引入革命并开始改变一切。
    【解决方案3】:
    {
      std::unique_ptr<int> i(new int);
    }
    

    【讨论】:

    • 这就是修复。这不是如何找到错误。
    • @LightnessRacesinOrbit 问题是“如何执行安全删除”,不是吗?
    • 没有。它是“如何执行安全删除(首先检查指针指向的区域是否已被删除)?”您必须阅读全部!他在问他是否可以让delete 对以前的deletes 免疫。答案是不”。完全正确,他可以而且应该通过不写 any deletes 来避免整个事情,但这不是所述问题的答案。
    • @LightnessRacesinOrbit 我读到括号中的部分是 OP 关于安全删除如何工作的假设。我是说这个假设是错误的。无论如何,您可以说与 RAII 等效的delete 是关闭范围,并且关闭范围两次是安全的。如果你愿意,我可以多加一对牙套!
    【解决方案4】:

    现代 C++ 解决方案是永远不要使用 newdelete。只需让 C++ 自动处理一切。

    unique_ptr<int> i = make_unique<int>();
    

    shared_ptr<int> i = make_shared<int>();
    

    无需删除。如果你没有make_unique,你可以write your own

    【讨论】:

      【解决方案5】:

      delete 不会尝试检测指针是否有效,它只是释放传递给它的指针。每次删除后可以将i设置为nullptr。并在再次删除之前检查if(i==nullptr)(虽然再次删除nullptr 不会导致任何问题,因为删除nullptr没有操作,它实际上什么都不做)。

      如果您只是在玩,那么这种代码可能有助于更好地了解该语言。但是在生产中你应该小心这些类型的代码并消除它们。这也是一个很好的指标,表明您的代码可能存在其他资源管理错误。

      【讨论】:

      • 不用检查,删除null已经定义好了。
      • 我不太喜欢“检测并忽略”或“静默失败”的解决方案。双重删除指针应该检测并修复错误
      • @AdrianoRepetti,对。我只是想回答OP的问题。编辑了我的答案。
      猜你喜欢
      • 1970-01-01
      • 2016-03-11
      • 1970-01-01
      • 1970-01-01
      • 2018-02-22
      • 1970-01-01
      • 2021-10-03
      • 2012-03-13
      • 2018-02-02
      相关资源
      最近更新 更多