1

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

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

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

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

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

4

4 回答 4

7

请注意,您的代码

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

简化为:

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

delete在 C++ 中对空指针调用运算符是安全的。它什么也不做。换句话说,对 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 问题:部分使用一些序列号等,或者通过实施您自己的垃圾收集方案。)

于 2013-04-04T21:18:00.313 回答
2

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

于 2013-04-04T21:12:25.767 回答
0

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

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

于 2013-04-04T22:06:02.513 回答
-2
...
... 
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"
...
...

不用说,不要在实际代码中这样做。希望这可以帮助。

于 2013-04-04T21:13:53.137 回答