As shown here, one can use dynamic_cast
to detect a deleted pointer:
#include <iostream>
using namespace std;
class A
{
public:
A() {}
virtual ~A() {}
};
class B : public A
{
public:
B() {}
};
int main()
{
B* pB = new B;
cout << "dynamic_cast<B*>( pB) ";
cout << ( dynamic_cast<B*>(pB) ? "worked" : "failed") << endl;
cout << "dynamic_cast<B*>( (A*)pB) ";
cout << ( dynamic_cast<B*>( (A*)pB) ? "worked" : "failed") << endl;
delete pB;
cout << "dynamic_cast<B*>( pB) ";
cout << ( dynamic_cast<B*>(pB) ? "worked" : "failed") << endl;
cout << "dynamic_cast<B*>( (A*)pB) ";
cout << ( dynamic_cast<B*>( (A*)pB) ? "worked" : "failed") << endl;
}
the output:
dynamic_cast<B*>( pB) worked
dynamic_cast<B*>( (A*)pB) worked
dynamic_cast<B*>( pB) worked
dynamic_cast<B*>( (A*)pB) failed
It explains that the deletion of the vtable is detected.
But I am wondering how is that possible since we do not overwrite the freed memory?
And is that solution fully portable ?
Thanks
First off, trying to use a deleted object in any form results in undefined behavior: whatever result you see could happen!
The reason of the observed behavior is simply that an object changes type during destruction: from being an object of the concrete type it change through all of the types in the hierarchy. At each point the virtual functions change and the vtable (or similar) gets replaced. The dynamic_cast<...>()
simply detects this change in the bytes strored at the location of the object.
In case you feel like wanting to show that this technique doesn't reliably work you can just set the content of deleted memory to a random bit pattern or the bit pattern of an object of the most derived type: a random bit pattern probably yields a crash and memcpy()
probably claims that the object is still life. Of course, since it is undefined behavior anything can happen.
One relevant section on this 3.8 [basic.life] paragraph 5:
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a pointer refers to allocated storage (3.7.4.2), and using the pointer as if the pointer were of type
void*
, is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if:
- ...
- the pointer is used as the operand of a dynamic_cast (5.2.7). ...
Oddly, the example on the last bullet on dynamic_cast
doesn't use dynamic_cast
.
Of course, the object is also probably released in which case the above guarantees don't even apply.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With