I have a program which uses a custom allocator and deallocator to manage memory. I've recently encountered a leak that has lead me down a huge rabbit hole that end with custom deleters being incapable of handling multiple inheritance. In the code sample below:
#include <iostream>
#include <memory>
using namespace std;
class Arena {};
void* operator new(std::size_t size, const Arena&) {
auto ptr = malloc(size);
cout << "new " << ptr << endl;
return ptr;
}
void operator delete(void* ptr, const Arena&) {
cout << "delete " << ptr << endl;
free(ptr);
}
class A
{
public:
virtual ~A() = default;
};
class B
{
public:
virtual ~B() = default;
};
class AB : public A, public B
{
public:
~AB() override = default;
};
int main()
{
B* ptr = new (Arena()) AB;
ptr->~B();
operator delete(ptr, Arena());
return 0;
}
The output is:
new 0x55e20c8a6eb0
delete 0x55e20c8a6eb8
free(): invalid pointer
Because the address of B is a vtable somewhere inside AB. Using the builtin delete ptr
function leads to the pointer being returned to it's original value and freed successfully. I've found some information about top_offset being used to address this here, but this is implementation dependent. So, is there a way to convert a pointer to B back into a pointer to AB without knowing anything about AB?
CodePudding user response:
You can do it this way:
void* dptr = dynamic_cast<void*>(ptr);
ptr->~B();
operator delete(dptr, Arena());
Note you need to dynamic_cast before destroying the B
object.
Without RTTI things get hairy. I assume that in your real code you need the identity of the arena object (otherwise it would be trivial to define member operators new/delete that just pull an arena out of thin air and redirect to global placement new/delete). You need to store this identity somewhere. Hmm, if we only could dynamically allocate some memory for it... wait a minute... we are allocating memory, we can store it there, just increase the size appropriately...
union AlignedArenaPtr {
Arena* arena;
std::max_align_t align;
};
struct Base { // inherit everything from this
virtual ~Base() = default;
void* operator new(std::size_t size, Arena *arena) {
auto realPtr = (AlignedArenaPtr*)::operator new(size
sizeof(AlignedArenaPtr), arena);
realPtr->arena = arena;
return realPtr 1;
}
void operator delete(void* ptr) {
auto realPtr = ((AlignedArenaPtr*)(ptr)) - 1;
::operator delete(realPtr, realPtr->arena);
}
void* operator new(std::size_t size) = delete; // just in case
};