考虑这个程序:
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()
之前进行的分配越多,崩溃的可能性就越大。
无论是什么原因,这对我的实际线程代码来说都是一个问题。鉴于上述情况,我如何可靠地判断另一个线程是否删除了当前想要访问的对象?
答案 0 :(得分:8)
没有平台无关的方法来检测这个,没有让其他线程在删除对象后将指针设置为NULL,最好是在临界区内,或等效的。
简单的解决方案是:设计您的代码,以便不会发生这种情况。不要删除其他线程可能需要的对象。只有在安全的情况下才能清除共享资源。
答案 1 :(得分:4)
我原本期待一场车祸,但相反 发生了这件事:
这是因为Speak()没有访问该类的任何成员。编译器不会为您验证指针,因此它像任何其他函数调用一样调用Speak(),将(已删除)指针作为隐藏的'this'参数传递。由于Speak()不能访问任何参数,因此没有理由崩溃。
答案 2 :(得分:3)
我原本以为发生了崩溃,但发生了这种情况:
未定义的行为意味着任何事情都可能发生。
答案 3 :(得分:2)
鉴于上述情况,我如何可靠地判断另一个线程是否删除了当前想要访问的对象?
如何将MyTest
指针设置为零(或NULL)。这将使其他线程明确表示它不再有效。 (当然,如果你的其他线程有自己的指针指向相同的内存,那么,你设计的东西是错误的。不要删除其他线程可能使用的内存。)
此外,你绝对不能指望它以它的方式运作。那很幸运。有些系统会在删除后立即破坏内存。
答案 4 :(得分:2)
尽管最好改进设计以避免访问已删除的对象,但您可以添加调试功能以查找访问已删除对象的位置。
这个脏技巧导致所有函数调用读取指针指向的地址,并在大多数系统上导致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_ptr
或boost::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。由于访问是序列化的,因此您将获得一致的操作。