【问题标题】:C++: How to verify a deleted pointerC++:如何验证已删除的指针
【发布时间】:2013-03-27 03:34:06
【问题描述】:

我目前正在大学学习 C++ 中的指针。我编写了一个程序,它是一个指向子对象链表的对象二叉树。如果我的措辞正确的话。无论如何,我的程序似乎工作正常,但我无法解决如何测试指针删除的问题。

例如,我对二叉树的单个对象的删除函数是:

    void EmployeeRecord::destroyCustomerList()
    {
        if(m_oCustomerList != NULL)
        {
            delete m_oCustomerList;
            m_oCustomerList = NULL;           
        }
    }

打印我的树时,所有内容都会填充并正确移除(这意味着树在每次删除节点时都保持完整)......但是我如何确认释放的内存会发生什么?我知道,由于我将指针 *m_oCustomerList 设置为 NULL,因此我可以在先前填充的对象上测试 NULL 值,但实际内存会发生什么情况?

我正在使用 Visual Studio/C++ 并且已经读到调试器将使用从 0xCC 开始的代码来释放内存...但我似乎无法弄清楚如何使用该信息。

【问题讨论】:

  • 为什么要验证?您是否怀疑编译器是否正常工作?
  • 您可以使用Valgrind 来帮助检测和调试内存泄漏。
  • stackoverflow.com/questions/15730827/… 可能重复?我会使用 smart_ptrs 或在析构函数中添加一些东西以进行调试/测试以确认删除。
  • 确实没有理由验证delete 是否有效。确实如此。删除时内存会发生什么取决于编译器设计者,但通常它会返回到堆中以供将来使用。
  • 您也可以使用垃圾收集器,根本不必删除。

标签: c++


【解决方案1】:

注意你的代码

    void EmployeeRecord::destroyCustomerList()
    {
        if(m_oCustomerList != NULL)
        {
            delete m_oCustomerList;
            m_oCustomerList = NULL;           
        }
    }

简化为:

    void EmployeeRecord::destroyCustomerList()
    {
        delete m_oCustomerList;
        m_oCustomerList = NULL;
    } 

在 C++ 中对空指针调用 delete 运算符是安全的。它什么也不做。换句话说,对 null 的检查已经“内置”了。

一旦你删除了一个对象,它就不再存在了,指向那个对象的指针变成了不确定的值(所以把那个指针的所有副本都清空并不是一个坏主意)。

在实际的 C++ 实现中,内存发生的真正情况,而不是抽象意义上的,是它继续存在于同一地址,但被标记为空闲,因此它可以分配给其他目的。来自程序(可能是完全不相关的模块)或可能来自系统中另一个程序的分配请求可以获得该内存供自己使用。

任何指向不再存在的对象的指针的使用都是“未定义的行为”。确实存在用于安全验证此类指针的函数,但它们非常特定于平台且很少完美。

问题在于,虽然对于一个实现来说确认一个指针是坏的并不是特别困难,但不可能确认一个指针是好的。我们可以遍历内存分配器的内部内存数据结构,以确定某个指针指向空闲存储。但是如果存储随后被分配怎么办?然后指针不再指向空闲存储。但它也不是指分配的原始对象!这被称为“ABA 歧义”:因为一些 A 变成了 B,然后又变成了 A,与原来的 A 没有区别。

存在解决 ABA 歧义的方法(如果不是完全解决,至少是部分解决)。例如,指针变得“胖”,因此它们除了地址位之外还有一个额外的字段。该字段可以包含一个序列号,用于标记从分配器返回的指针。现在,当一个对象被删除并重新分配时,指向同一位置的新指针具有不同的序列号:我们有 ABA'。指针 A 坏了,变成了 B,但是当它复活时,它又变成了 A'。如果我们要求系统验证 A,它会正确地确定 A 是坏的,因为它没有预期的序列号。指向对象的正确有效指针是 A',它与 A 不匹配。

但是,序列号字段只有这么多位宽,它们最终会回绕。所以ABA问题并没有真正解决。好与坏指针的验证只会变得更加可靠。为了彻底解决 ABA 问题,系统必须始终分发不等于任何仍可使用的指针的新指针。这意味着永远不要真正释放任何东西(从而耗尽内存)或实施垃圾收集。 (这意味着delete 实际上什么都不做:被删除的对象会被破坏,但会一直保留在内存中,直到它们被垃圾回收,当程序不再记住指针的任何副本时,就会发生这种情况。此时,程序不再记得A,所以可以重新引入A,不存在ABA问题。)

要使 所有 指针“变胖”,您必须更改整个工具链和运行时:编译器、库等。由于大型程序往往具有多个内存分配器,因此存在进一步的困难。如果你问错误的分配器“这个指针是否有效”,它只能说“这个指针不是来自我的竞技场”。您可以做的另一种方法是发明自己的指针并将它们实现为 C++ 中的智能指针。您的指针可以支持is_valid 方法,该方法试图尽可能可靠(以某种方式处理 ABA 问题:部分使用一些序列号等,或者通过实施您自己的垃圾收集方案。)

【讨论】:

  • 嗯,我不知道。谢谢。
【解决方案2】:

访问已删除的内存是标准未定义的行为。例如,如果这是一个多线程应用程序(或某些其他进程已将线程注入您的应用程序),那么新分配可能会在您能够“验证”之前分配您刚刚释放的内存。

【讨论】:

    【解决方案3】:

    一旦你删除了你的内存并将你的指针设置为 NULL,即使你想要它,你也无法再访问该内存。因此,无法验证它是否真的消失了。但是,如果您做错了什么并且内存从未被删除,它将包含内存泄漏,这会导致您的程序增加它使用的内存量,您可以将此视为指针未正确处理的症状。

    稍后您可能会了解到,您不必担心指针的删除,因为std::shared_ptr 会在指针超出范围时删除您的对象。稍后会更安全,因为您可能会了解到异常会导致您的析构函数永远不会触发而导致内存泄漏。

    【讨论】:

      【解决方案4】:
      ...
      ... 
      delete m_oCustomerList;
      
      // Try using the deleted pointer here
      // This should cause a runtime exception
      // which means you did free the pointer
      m_oCustomerList->someStrMemberVariable = "This will fail"
      ...
      ...
      

      不用说,不要在实际代码中这样做。希望这会有所帮助。

      【讨论】:

      • 这个答案是错误的。访问已删除的指针是未定义的行为,不保证会导致运行时错误。
      • 我同意,至少对我来说...尝试访问 NULL 指针和删除的指针似乎做同样的事情...即使程序崩溃。
      • @user2079828 访问空指针会在几乎所有操作系统上产生运行时错误,但访问已删除指针是运气问题。大多数情况下,指针仍会指向有效内存,不会发生运行时错误。
      猜你喜欢
      • 2017-10-08
      • 2010-09-08
      • 2013-09-09
      • 2018-05-14
      • 1970-01-01
      • 1970-01-01
      • 2011-09-18
      • 1970-01-01
      相关资源
      最近更新 更多