Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the C++ delete operator find the memory location of a polymorphic object?

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.
like image 503
Anton Avatar asked Jul 31 '12 17:07

Anton


People also ask

How does operator delete work?

operator delete, operator delete[] Deallocates storage previously allocated by a matching operator new. These deallocation functions are called by delete-expressions and by new-expressions to deallocate memory after destructing (or failing to construct) objects with dynamic storage duration.

What is the purpose of delete operator in C?

The delete operator is used to deallocate the memory. User has privilege to deallocate the created pointer variable by this delete operator.

What does the delete operator do in addition to deallocating the memory used by the object?

When delete is used to deallocate memory for a C++ class object, the object's destructor is called before the object's memory is deallocated (if the object has a destructor). If the operand to the delete operator is a modifiable l-value, its value is undefined after the object is deleted.

Which operator is used to destroy the memory?

Delete is an operator that is used to destroy array and non-array(pointer) objects which are created by new expression. New operator is used for dynamic memory allocation which puts variables on heap memory. Which means Delete operator deallocates memory from heap.


2 Answers

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:

  1. 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:

    void *derrived = dynamic_cast<void*>(some_ptr); 

    Which gets the C* back from just a B*, for example:

    #include <iostream>  struct A {     unsigned a;     virtual ~A() { } };  struct B {     unsigned b;     virtual ~B() { } };  struct C : public A, public B {     unsigned c; };  int main() {   C* c = new C;   std::cout << static_cast<void*>(c) << "\n";   B* b = c;   std::cout << static_cast<void*>(b) << "\n";   std::cout << dynamic_cast<void*>(b) << "\n";    delete b; } 

    Gave the following on my system

     0x912c008 0x912c010 0x912c008 
  2. 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:

    #include <iostream> #include <stdlib.h> #include <malloc.h>  int main() {   char *test = (char*)malloc(50);   std::cout << malloc_usable_size(test) << "\n"; } 

    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_size are given in the libc source code, in malloc/malloc.c:

(The following includes lightly edited explanations by Colin Plumb.)

Chunks of memory are maintained using a `boundary tag' method as described in e.g., Knuth or Standish. (See the paper by Paul Wilson ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a survey of such techniques.) Sizes of free chunks are stored both in the front of each chunk and at the end. This makes consolidating fragmented chunks into bigger chunks very fast. The size fields also hold bits representing whether chunks are free or in use.

An allocated chunk looks like this:

     chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+             |             Size of previous chunk, if allocated            | |             +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+             |             Size of chunk, in bytes                       |M|P|       mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+               |             User data starts here...                          .               .                                                               .               .             (malloc_usable_size() bytes)                      .               .                                                               |    nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                  |             Size of chunk                                     |               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+   
like image 52
Flexo Avatar answered Oct 04 '22 09:10

Flexo


Destroying a base class pointer requires that you've implemented a virtual destructor. If you didn't do that, all bets are off.

The first destructor called will be the one for the most derived object as determined by the virtual mechanism (vtable). This destructor knows the size of the object! It can squirrel that information away someplace, or pass it down the chain of destructors.

like image 29
Mark Ransom Avatar answered Oct 04 '22 07:10

Mark Ransom