开发者

Detecting when a "new" item has been deleted [duplicate]

This question already has answers here: How can I determine if a C++ object has been deallocated? (6 answers) Closed 4 years ago.

Consider this program:

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");
}

I was expecting a crash, but instead this happened:

Hello

Goodbye

I say!

I'm guessing this is because when memory is marked as deallocated it isn't physically wiped, and since the code references it straight away the object is still to be found there, wholly intact. The more allocations made before calling Speak() the more likely a crash.

Whatever the reason, this is a problem for my actual, threaded code. Given the above, how can I reliably tell if another thread has deleted an object that the开发者_StackOverflow社区 current one wants to access?


There is no platform-independent way of detecting this, without having the other thread(s) set the pointer to NULL after they've deleted the object, preferably inside a critical section, or equivalent.

The simple solution is: design your code so that this can't occur. Don't delete objects that might be needed by other threads. Clear up shared resource only once it's safe.


I was expecting a crash, but instead this happened:

That is because Speak() is not accessing any members of the class. The compiler does not validate pointers for you, so it calls Speak() like any other function call, passing the (deleted) pointer as the hidden 'this' parameter. Since Speak() does not access that parameter for anything, there is no reason for it to crash.


I was expecting a crash, but instead this happened:

Undefined Behaviour means anything can happen.


Given the above, how can I reliably tell if another thread has deleted an object that the current one wants to access?

How about you set the MyTest pointer to zero (or NULL). That will make it clear to other threads that it's no longer valid. (of course if your other threads have their own pointers pointing to the same memory, well, you've designed things wrong. Don't go deleting memory that other threads may use.)

Also, you absolutely can't count on it working the way it has. That was lucky. Some systems will corrupt memory immediately upon deletion.


Despite it's best to improve the design to avoid access to a deleted object, you can add a debug feature to find the location where you access deleted objects.

  • Make all methods and the destructor virtual.
  • Check that your compiler creates an object layout where the pointer to the vtable is in front of the object
  • Make the pointer to the vtable invalid in the destructor

This dirty trick causes that all functions calls reads the address where the pointer points to and cause a NULL pointer exception on most systems. Catch the exception in the debugger.

If you hesitate to make all methods virtual, you can also create an abstract base class and inherit from this class. This allows you to remove the virtual function with little effort. Only the destructor needs to be virtual inside the class.

example

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"; }
};


You might use reference counting in this situation. Any code that dereferences the pointer to the allocated object will increment the counter. When it's done, it decrements. At that time, iff the count hits zero, deletion occurs. As long as all users of the object follow the rules, nobody access the deallocated object.

For multithreading purposes I agree with other answer that it's best to follow design principles that don't lead to code 'hoping' for a condition to be true. From your original example, were you going to catch an exception as a way to tell if the object was deallocated? That is kind of relying on a side effect, even if it was a reliable side effect which it's not, which I only like to use as a last resort.


This is not a reliable way to "test" if something has been deleted elsewhere because you are invoking undefined behavior - that is, it may not throw an exception for you to catch.

Instead, use std::shared_ptr or boost::shared_ptr and count references. You can force a shared_ptr to delete it's contents using shared_ptr::reset(). Then you can check if it was deleted later using shared_ptr::use_count() == 0.


You could use some static and runtime analyzer like valgrind to help you see these things, but it has more to do with the structure of your code and how you use the language.


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

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


One solution, not the most elegant...

Place mutexes around your list of objects; when you delete an object, mark it as null. When you use an object, check for null. Since access is serialized, you'll have a consistent operation.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜