jwd
jwd

Reputation: 11144

Why does a virtual destructor write to memory?

Recently, when working with custom allocator code and placement new+delete, I noticed something that surprised me: When a virtual destructor is called, it writes to the object's soon-to-be-freed memory.

Why is that?

(update) Aside: I'm more interested in the actual behavior here, not what the C++ standard has to say, which I'm sure does not specify this behavior.

Here's a small program to demonstrate:

#include <new>
#include <cstring>
#include <iostream>

using std::cout;
using std::endl;

struct Interface {
    virtual ~Interface() = default;
};

struct Derived : public Interface {
};

alignas(Derived) unsigned char buffer[sizeof(Derived)];

int main() {

    memset(buffer, 0xff, sizeof(buffer));
    cout << "Initial first byte: 0x" << std::hex << (int)buffer[0] << endl;
    
    // Create an instance, using 'data' as storage
    Derived *pDer = ::new (buffer) Derived();
    cout << "After ctor, first byte: 0x" << std::hex << (int)buffer[0] << endl;
    
    pDer->~Derived();
    
    cout << "After destroy, first byte: 0x" << std::hex << (int)buffer[0] << endl;

    return 0;
}

Live link: https://godbolt.org/z/jWv6qs3Wc

Here is the output:

Initial first byte: 0xff
After ctor, first byte: 0x68
After destroy, first byte: 0x88

If I remove the virtual Interface, then the memory never changes at all, as expected.

Is this some kind of debug functionality?

It seems compiler-specific. Clang does not do it, but GCC does.

It does seem to go away with -O2. But still, I'm not sure it's purpose.

Upvotes: 3

Views: 136

Answers (1)

Artyer
Artyer

Reputation: 40881

To destroy a Derived, conceptually Derived::~Derived is called (which does nothing in this case), then the vtable is adjusted so that the object is an Interface, then Interface::~Interface is called. What you are observing is the pointer to the Interface vtable (as seen here, constructing an Interface gives the same first byte).

If you enable optimisations, then since Interface::~Interface does nothing, Derived::~Derived can be optimised to a no-op too, and you see the same first byte printed.

Upvotes: 10

Related Questions