This post starts with a fairly obscure topic - how an overloaded operator delete behaves in light of polymorphism; amazingly, it then gets even more obscure - shedding light on the trickery the compiler employs to make this work, by generating more than one destructor for certain classes. If you're into such things, read on. If not, sorry about that; I heard that three new Javascript libraries were released this week for MVC JSON-based dynamic CSS layout. Everyone's switching! Hurry up to keep up with the cool guys and leave this grumpy compiler engineer to mumble to himself.

Virtual operator delete? Consider this code sample: #include <cstdio> class Animal { public : virtual void say () = 0 ; virtual ~ Animal () {} }; class Sheep : public Animal { public : virtual void say () { printf ( "Sheep says baaaaa

" ); } virtual ~ Sheep () { printf ( "Sheep is dead

" ); } void operator delete ( void * p ) { printf ( "Reclaiming Sheep storage from %p

" , p ); :: operator delete ( p ); } }; int main ( int argc , char ** argv ) { Animal * ap = new Sheep ; ap -> say (); delete ap ; return 0 ; } What happens when ap is deleted? Two things: The destructor of the object pointed to by ap is called. operator delete is called on ap to reclaim heap storage. Part 1 is fairly clear: the static type of ap is Animal , but the compiler knows that Animal has a virtual destructor. So it looks up the actual destructor to invoke in the virtual table stored in the object ap points to. Since the dynamic type of ap is Sheep , the destructor found there will be Sheep::~Sheep , which is correct. What about that operator delete , though? Is operator delete virtual too? Is is also stored in the virtual table? Because if it isn't, how does the compiler know which operator delete to invoke? No, operator delete is not virtual. It is not stored in the virtual table. In fact, operator delete is a static member. The C++11 standard says so explicitly in secton 12.5: Any deallocation function for a class X is a static member (even if not explicitly declared static ). It also adds: Since member allocation and deallocation functions are static they cannot be virtual. And if you keep reading, it actually mandates that even though this is the case, when the base destructor is virtual operator delete will be correctly looked up in the scope of the class that is the dynamic, not the static type of the object. Indeed, the code snippet above works correctly and prints: Sheep says baaaaa Sheep is dead Reclaiming Sheep storage from 0x1ed1be0

Deleting destructor So how does this work, if operator delete is not virtual? Then answer is in a special destructor created for by the compiler. It's called the deleting destructor and its existence is described by the Itanium C++ ABI: deleting destructor of a class T - A function that, in addition to the actions required of a complete object destructor, calls the appropriate deallocation function (i.e,. operator delete ) for T. The ABI goes on to provide more details: The entries for virtual destructors are actually pairs of entries. The first destructor, called the complete object destructor, performs the destruction without calling delete() on the object. The second destructor, called the deleting destructor, calls delete() after destroying the object. So now the mechanics of this operation should be fairly clear. The compiler mimics "virtuality" of operator delete by invoking it from the destructor. Since the destructor is virtual, what ends up called eventually is the destructor for the dynamic type of the object. In our example this would be the destructor of Sheep , which can call the right operator delete since it's in the same static scope. However, as the ABI says, such classes need two destructors. If an object is destructed but not deleted from the heap, calling operator delete is wrong. So a separate version of the destructor exists for non- delete destructions.

Examining how the compiler implements deleting destructors That's quite a bit of theory. Let's see how this is done in practice by studying the machine code generated by gcc for our code sample. First, I'll slightly modify main to invoke another function that just creates and discards a new Sheep without involving the heap. void foo () { Sheep s ; } int main ( int argc , char ** argv ) { Animal * ap = new Sheep ; ap -> say (); delete ap ; foo (); return 0 ; } And compiling this with the flags : g++ -O2 -g -static -std=c++11 -fno-inline -fno-exceptions We get the following disassembly for main . I've annotated the disassembly with comments to explain what's going on: 0000000000400 cf0 < main >: 400 cf0 : push % rbx 400 cf1 : mov $ 0x8 , % edi // Call operator new to allocate a new object of type Sheep, and call // the constructor of Sheep. Neither Sheep nor Animal have fields, so // their size is 8 bytes for the virtual table pointer. // The pointer to the object will live in %rbx. The vtable pointer in this // object (set up by the constructor of Sheep) points to the the virtual // table of Sheep, because this is the actual type of the object (even // though we hold it by a pointer to Animal here). 400 cf6 : callq 401750 < _Znwm > 400 cfb : mov % rax , % rbx 400 cfe : mov % rax , % rdi 400 d01 : callq 4011f 0 < _ZN5SheepC1Ev > // The first 8 bytes of an Animal object is the vtable pointer. So move // the address of vtable into %rax, and the object pointer itself ("this") // into %rdi. // Since the vtable's first entry is the say() method, the call that // actually happens here is Sheep::say(ap) where ap is the object pointer // passed into the (implicit) "this" parameter. 400 d06 : mov ( % rbx ), % rax 400 d09 : mov % rbx , % rdi 400 d0c : callq * ( % rax ) // Once again, move the vtable address into %rax and the object pointer // into %rdi. This time, invoke the function that lives at offset 0x10 in // the vtable. This is the deleting destructor, as we'll soon see. 400 d0e : mov ( % rbx ), % rax 400 d11 : mov % rbx , % rdi 400 d14 : callq * 0x10 ( % rax ) // Finally call foo() and return. 400 d17 : callq 4010 d0 < _Z3foov > 400 d1c : xor % eax , % eax 400 d1e : pop % rbx 400 d1f : retq A diagram of the memory layout of the virtual table for Sheep can be helpful here. Since neither Animal nor Sheep have any fields, the only "contents" of a Sheep object is the vtable pointer which occupies the first 8 bytes: Virtual table for Sheep: ap: -------------- ----------------------- | vtable ptr | ---------> | Sheep::say() | 0x00 -------------- ----------------------- | Sheep::~Sheep() | 0x08 ----------------------- | Sheep deleting dtor | 0x10 ----------------------- The two destructors seen here have the roles described earlier. Let's see their annotated disassembly: // Sheep::~Sheep 0000000000401140 < _ZN5SheepD1Ev >: // Call printf("Sheep is dead

") 401140 : push % rbx 401141 : mov $ 0x49dc7c , % esi 401146 : mov % rdi , % rbx 401149 : movq $ 0x49dd50 ,( % rdi ) 401150 : xor % eax , % eax 401152 : mov $ 0x1 , % edi 401157 : callq 446260 < ___printf_chk > 40115 c : mov % rbx , % rdi 40115f : pop % rbx // Call Animal::~Animal, destroying the base class. Note the cool tail // call here (using jmpq instead of a call instruction - control does not // return here but the return instruction from _ZN6AnimalD1Ev will return // straight to the caller). 401160 : jmpq 4010f 0 < _ZN6AnimalD1Ev > 401165 : nopw % cs : 0x0 ( % rax , % rax , 1 ) 40116f : nop // Sheep deleting destructor. The D0 part of the mangled name for deleting // destructors, as opposed to D1 for the regular destructor, is mandated by // the ABI name mangling rules. 00000000004011 c0 < _ZN5SheepD0Ev >: 4011 c0 : push % rbx // Call Sheep::~Sheep 4011 c1 : mov % rdi , % rbx 4011 c4 : callq 401140 < _ZN5SheepD1Ev > 4011 c9 : mov % rbx , % rdi 4011 cc : pop % rbx // Call Sheep::operator delete 4011 cd : jmpq 401190 < _ZN5SheepdlEPv > 4011 d2 : nopw % cs : 0x0 ( % rax , % rax , 1 ) 4011 dc : nopl 0x0 ( % rax ) Now, going back to the amended code sample, let's see what code is generated for foo : 00000000004010 d0 < _Z3foov >: 4010 d0 : sub $ 0x18 , % rsp 4010 d4 : mov % rsp , % rdi 4010 d7 : movq $ 0x49dd30 ,( % rsp ) 4010 df : callq 401140 < _ZN5SheepD1Ev > 4010e4 : add $ 0x18 , % rsp 4010e8 : retq 4010e9 : nopl 0x0 ( % rax ) foo just calls Sheep::~Sheep . It shouldn't call the deleting destructor, because it does not actually delete an object from the heap. It is also interesting to examine how the destructor(s) of Animal look, since unlike Sheep , Animal does not define a custom operator delete : // Animal::~Animal 00000000004010f 0 < _ZN6AnimalD1Ev >: 4010f 0 : movq $ 0x49dcf0 ,( % rdi ) 4010f 7 : retq 4010f 8 : nopl 0x0 ( % rax , % rax , 1 ) // Animal deleting destructor 0000000000401100 < _ZN6AnimalD0Ev >: 401100 : push % rbx // Call Animal::~Animal 401101 : mov % rdi , % rbx 401104 : callq 4010f 0 < _ZN6AnimalD1Ev > 401109 : mov % rbx , % rdi 40110 c : pop % rbx // Call global ::operator::delete 40110 d : jmpq 4011f 0 < _ZdlPv > 401112 : nopw % cs : 0x0 ( % rax , % rax , 1 ) 40111 c : nopl 0x0 ( % rax ) As expected, the destructor of Animal calls the global ::operator delete .