Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I have two destructor implementations in my assembly output? [duplicate]

And objdump of my .o file reveals that I have two different destructors for the same class. Why?

Disassembly of section .text._ZN1AD0Ev:

0000000000000000 <_ZN1AD0Ev>:
   0:   53                      push   %rbx
   1:   be 00 00 00 00          mov    $0x0,%esi
   6:   48 89 fb                mov    %rdi,%rbx
   9:   48 c7 07 00 00 00 00    movq   $0x0,(%rdi)
  10:   ba 2c 00 00 00          mov    $0x2c,%edx
  15:   bf 00 00 00 00          mov    $0x0,%edi
  1a:   e8 00 00 00 00          callq  1f <_ZN1AD0Ev+0x1f>
  1f:   48 89 df                mov    %rbx,%rdi
  22:   be 08 00 00 00          mov    $0x8,%esi
  27:   5b                      pop    %rbx
  28:   e9 00 00 00 00          jmpq   2d <_ZN1AD0Ev+0x2d>

Disassembly of section .text._ZN1AD2Ev:

0000000000000000 <_ZN1AD1Ev>:
   0:   48 c7 07 00 00 00 00    movq   $0x0,(%rdi)
   7:   ba 2c 00 00 00          mov    $0x2c,%edx
   c:   be 00 00 00 00          mov    $0x0,%esi
  11:   bf 00 00 00 00          mov    $0x0,%edi
  16:   e9 00 00 00 00          jmpq   1b <_ZN1AD1Ev+0x1b>

These are the classes in the header file that result in this code being generated:

#include <iostream>

class A {
 public:
   virtual ~A() {
      ::std::cout << "This destructor does something significant.\n";
   }
};

class B : public A {
 public:
   inline virtual ~B() = 0;
};

B::~B() = default;

class C : public B {
 public:
   inline virtual ~C() = default;
};
like image 947
Omnifarious Avatar asked Jun 15 '17 03:06

Omnifarious


2 Answers

Many compilers generate two different destructors for one class: one for destroying dynamically allocated objects, another - for destroying non-dynamic objects (static objects, local objects, base sub-objects or member sub-objects). The former calls operator delete from inside, the latter doesn't. Some compilers do it by adding a hidden parameter to one destructor (older versions of GCC do it that way, MSVC++ does it that way), some compilers simply generate two separate destructors (newer versions of GCC do it that way).

The need to call operator delete from inside the destructor arises from C++ specification, which says that the proper operator delete should be chosen "as if" it was looked up from inside the (possibly virtual) destructor of the most derived object. So, operator delete, which can be implemented as a static member function should behave as if it were a virtual function.

Most implementations implement this requirement "literally": they not only look up the proper operator delete from inside the destructor, they actually call it from there.

Of course, operator delete only has to be called from the most derived object's destructor, and only if that object was dynamically allocated. This is where that hidden parameter (or two versions of destructor) come into the picture.

like image 132
AnT Avatar answered Nov 19 '22 23:11

AnT


GCC follows the Itanium ABI:

Starting with GCC 3.2, GCC binary conventions for C++ are based on a written, vendor-neutral C++ ABI that was designed to be specific to 64-bit Itanium ...

The Itanium ABI specifies different destructors:

  <ctor-dtor-name> ::= C1   # complete object constructor
           ::= C2   # base object constructor
           ::= C3   # complete object allocating constructor
           ::= D0   # deleting destructor
           ::= D1   # complete object destructor
           ::= D2   # base object destructor

The number convention can be seen in your assembly output (the difference between the name mangling in the two functions is 0 and 1).

Finally, here's a description of the difference between these two destructors:

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. Both destroy any virtual bases; a separate, non-virtual function, called the base object destructor, performs destruction of the object but not its virtual base subobjects, and does not call delete()

Further, this only happens if your class has a virtual destructor:

This ABI does not require the generation or use of allocating constructors or deleting destructors for classes without a virtual destructor. However, if an implementation emits such functions, it must use the external names specified in this ABI. If such a function has external linkage, it must be emitted wherever referenced, in a COMDAT group whose name is the external name of the function.

like image 31
user8163793 Avatar answered Nov 19 '22 21:11

user8163793