I would like to know how the delete operator figures out the memory location that needs to be freed when it is given a base class pointer that is different from the true memory location of the object.
I want to duplicate this behavior in my own custom allocator/deallocator.
Consider the following hierarchy:
struct A
{
unsigned a;
virtual ~A() { }
};
struct B
{
unsigned b;
virtual ~B() { }
};
struct C : public A, public B
{
unsigned c;
};
I want to allocate an object of type C and delete it through a pointer of type B. As far as I can tell this is a valid use of operator delete, and it works under Linux/GCC:
C* c = new C;
B* b = c;
delete b;
The interesting thing is that the pointers ‘b’ and ‘c’ actually point to different addresses because of how the object is laid out in memory, and the delete operator “knows” how to find and free the correct memory location.
I know that, in general, it is not possible to find the size of a polymorphic object given a base class pointer: Find out the size of a polymorphic object. I suspect that it is not generally possible to find the true memory location of the object either.
Notes:
- My question is not related to how new[] and delete[] work. I am interested in the single object allocation case. How does delete[] "know" the size of the operand array?.
- I am not concerned about how the destructor is called either. I am interested in the deallocation of the memory itself. How 'delete' works when I delete a pointer of base class
- I tested using -fno-rtti and -fno-exceptions, so G++ should not have access to runtime type information.
This is clearly implementation specific. In practice there are a relatively small number of sensible ways to implement things. Conceptually there are a few problems here:
You need to be able to get a pointer to the most derived object, that is the object that (conceptually) encompasses all of the other types.
In standard C++ you can do this with a
dynamic_cast:Which gets the
C*back from just aB*, for example:Gave the following on my system
Once that’s done it then becomes a standard memory allocation tracking problem. Usually that’s done in one of two ways, either a) record the size of the allocation just before the allocated memory, finding the size is just a pointer subtraction then or b) record allocations and free memory in a data structure of some sort. For more details see this question, which has a good reference.
With glibc you can query the size of a given allocation fairly sensibly:
That information is available to free/delete similarly and used to figure out what to do with the returned chunk of memory.
The exact details of the implementation of
malloc_useable_sizeare given in the libc source code, in malloc/malloc.c: