检测何时删除“新”项目

时间:2010-10-21 17:02:21

标签: c++ multithreading delete-operator

考虑这个程序:

int main()
{
    struct test
    {
        test() { cout << "Hello\n"; }
        ~test() { cout << "Goodbye\n"; }

        void Speak() { cout << "I say!\n"; }
    };

    test* MyTest = new test;
    delete MyTest;

    MyTest->Speak();

    system("pause");
}

我原本以为发生了崩溃,但发生了这种情况:

  

您好
  再见
  我说了!

我猜这是因为当内存被标记为已解除分配时,它不会被物理擦除,并且由于代码会立即引用它,因此仍然可以在那里找到对象,完全无损。在调用Speak()之前进行的分配越多,崩溃的可能性就越大。

无论是什么原因,这对我的实际线程代码来说都是一个问题。鉴于上述情况,我如何可靠地判断另一个线程是否删除了当前想要访问的对象?

10 个答案:

答案 0 :(得分:8)

没有平台无关的方法来检测这个,没有让其他线程在删除对象后将指针设置为NULL,最好是在临界区内,或等效的。

简单的解决方案是:设计您的代码,以便不会发生这种情况。不要删除其他线程可能需要的对象。只有在安全的情况下才能清除共享资源。

答案 1 :(得分:4)

  

我原本期待一场车祸,但相反   发生了这件事:

这是因为Speak()没有访问该类的任何成员。编译器不会为您验证指针,因此它像任何其他函数调用一样调用Speak(),将(已删除)指针作为隐藏的'this'参数传递。由于Speak()不能访问任何参数,因此没有理由崩溃。

答案 2 :(得分:3)

  

我原本以为发生了崩溃,但发生了这种情况:

未定义的行为意味着任何事情都可能发生。

答案 3 :(得分:2)

  

鉴于上述情况,我如何可靠地判断另一个线程是否删除了当前想要访问的对象?

如何将MyTest指针设置为零(或NULL)。这将使其他线程明确表示它不再有效。 (当然,如果你的其他线程有自己的指针指向相同的内存,那么,你设计的东西是错误的。不要删除其他线程可能使用的内存。)

此外,你绝对不能指望它以它的方式运作。那很幸运。有些系统会在删除后立即破坏内存。

答案 4 :(得分:2)

尽管最好改进设计以避免访问已删除的对象,但您可以添加调试功能以查找访问已删除对象的位置。

  • 将所有方法和析构函数设为虚拟。
  • 检查编译器是否创建了指针所在的对象布局 vtable位于对象前面
  • 在析构函数中使指向vtable的指针无效

这个脏技巧导致所有函数调用读取指针指向的地址,并在大多数系统上导致NULL指针异常。在调试器中捕获异常。

如果您犹豫使所有方法都是虚拟的,您还可以创建一个抽象基类并从该类继承。这允许您轻松删除虚拟功能。只有析构函数需要在类中是虚拟的。

例如

struct Itest
{
    virtual void Speak() = 0;
    virtual void Listen() = 0;
};

struct test : public Itest
{
    test() { cout << "Hello\n"; }
    virtual ~test() { 
        cout << "Goodbye\n";

        // as the last statement!
        *(DWORD*)this = 0;  // invalidate vtbl pointer
    }

    void Speak() { cout << "I say!\n"; }
    void Listen() { cout << "I heard\n"; }
};

答案 5 :(得分:1)

在这种情况下,您可以使用引用计数。任何取消引用指向已分配对象的指针的代码都会递增计数器。当它完成后,它会减少。那时,如果计数达到零,则发生删除。只要该对象的所有用户都遵循规则,nobody就会访问解除分配的对象。

对于多线程目的,我同意其他答案,即最好遵循不会导致代码“希望”条件成立的设计原则。从您的原始示例中,您是否要捕获异常作为判断对象是否已取消分配的方法?这有点依赖于副作用,即使它是一个可靠的副作用,但它不是,我只想作为最后的手段。

答案 6 :(得分:1)

这不是一种“测试”的可靠方法,如果某些内容已被删除,因为您正在调用未定义的行为 - 也就是说,它可能不会引发异常以便您捕获。

相反,请使用std::shared_ptrboost::shared_ptr并计算参考。您可以使用shared_ptr强制shared_ptr::reset()删除其内容。然后,您可以使用shared_ptr::use_count() == 0检查它是否稍后被删除。

答案 7 :(得分:0)

您可以使用像valgrind这样的静态和运行时分析器来帮助您查看这些内容,但它更多地与您的代码结构以及您如何使用该语言有关。

答案 8 :(得分:0)

// Lock on MyTest Here.
test* tmp = MyTest;
MyTest = NULL;
delete tmp;
// Unlock MyTest Here.

if (MyTest != NULL)
    MyTest->Speak();

答案 9 :(得分:0)

一个解决方案,而不是最优雅的......

将互斥锁放在对象列表周围;删除对象时,将其标记为null。使用对象时,请检查null。由于访问是序列化的,因此您将获得一致的操作。