Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ multiple inheritance and upcasted smart pointer destruction causes heap corruption in VS 2017

I have encountered an issue with VS debugger with the code above:

class Animal {
public:


};


class Stupid {
public:

};


class Dog : public Stupid, public Animal {
public:


};

int main() {
    std::unique_ptr<Animal> animal = std::unique_ptr<Dog>(new Dog());
    animal.reset();
    return 0;
}

This code throws an error after executing "animal.reset()" involving "ntdl.dll" and "wntdll.pdb".

Here are the expressions that generated assertion failures by MSVC Runtime library if I hit on "ignore" multiple(3) times:

1- _CrtIsValidHeapPointer(block)
2- is_block_type_valid(header->_block_use)
3- HEAP CORRUPTION DETECTED: before Free block (#-50331640) at 0x03737E21. CRT detected that the application wrote to memory before start of heap buffer.

But if I change the inheritance order of Dog, like this:

class Dog : public Animal, public Stupid {
public:


};

The code runs fine.

I have this error only in visual studio 2017, I have tried with Ideone, Android Studio and it runs fine no matter the inheritance order.

like image 516
someone Avatar asked Mar 07 '23 21:03

someone


1 Answers

This breaks because the pointer you pass to delete is not the same that you got back from new.

Up-casting basically means you take a pointer-to-derived and pretend it is a pointer-to-base. For single inheritance, this just works, as the base-part is always the first thing that is stored in a derived object. But with multiple inheritance, you have two bases!

Hence, when you up-cast to the second base, you actuall need to change the value of your pointer to make sure that what it's pointing to will actually be the respective base part of your object. You can verify that by examining the values of the pointers in the debugger:

Dog* d = new Dog;
Animal* a = d;

The a pointer will point one byte behind the d pointer.

As was already mentioned, this can be fixed by adding a virtual destructor to the base class type that you are using in the delete call (Animal in your example). This will cause the compiler to generate additional code to re-adjust the pointer correctly before passing it to delete.

Note that gcc actually implements the empty-base class optimization here, so this example will work there. Both bases will live at the same offset. It will start breaking there as well as soon as you start adding non-static data members to the base classes.

like image 148
ComicSansMS Avatar answered Apr 25 '23 08:04

ComicSansMS