Почему в выводе моей сборки есть две реализации деструктора?

А также objdump моего файла.o показывает, что у меня есть два разных деструктора для одного и того же класса. Зачем?

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>

Это классы в заголовочном файле, которые приводят к генерации этого кода:

#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;
};

2 ответа

Решение

Многие компиляторы генерируют два разных деструктора для одного класса: один для уничтожения динамически размещаемых объектов, другой - для уничтожения нединамических объектов (статических объектов, локальных объектов, базовых дочерних объектов или дочерних дочерних объектов). Бывшие звонки operator delete изнутри последний не делает. Некоторые компиляторы делают это, добавляя скрытый параметр к одному деструктору (более старые версии GCC делают это таким образом, MSVC++ делает это таким образом), некоторые компиляторы просто генерируют два отдельных деструктора (более новые версии GCC делают это таким образом).

Нужно позвонить operator delete изнутри деструктор возникает из спецификации C++, которая говорит, что надлежащий operator delete должен быть выбран "как если бы", он был найден изнутри (возможно, виртуального) деструктора самого производного объекта. Так, operator delete, которая может быть реализована как статическая функция-член, должна вести себя как виртуальная функция.

Большинство реализаций реализуют это требование "буквально": они не только ищут правильные operator delete изнутри деструктора, они на самом деле зовут его оттуда.

Конечно, operator delete должен вызываться только из деструктора самого производного объекта, и только если этот объект был динамически размещен. Вот где этот скрытый параметр (или две версии деструктора) входит в картину.

GCC следует за Itanium ABI:

Начиная с GCC 3.2, бинарные соглашения GCC для C++ основаны на написанном, независимом от производителя ABI-интерфейсе C++, который был разработан специально для 64-битного Itanium ...

Itanium ABI определяет различные деструкторы:

  <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

Соглашение о количестве можно увидеть в выходных данных вашей сборки (разница между искажением имени в двух функциях равна 0 и 1).

Наконец, вот описание различия между этими двумя деструкторами:

Записи для виртуальных деструкторов на самом деле являются парами записей. Первый деструктор, называемый полным деструктором объекта, выполняет уничтожение, не вызывая delete() для объекта. Второй деструктор, называемый деструктором удаления, вызывает delete() после уничтожения объекта. Оба уничтожают любые виртуальные базы; отдельная, не виртуальная функция, называемая деструктором базового объекта, выполняет уничтожение объекта, но не его виртуальных базовых подобъектов, и не вызывает delete()

Кроме того, это происходит только в том случае, если в вашем классе есть виртуальный деструктор:

Этот ABI не требует генерации или использования выделения конструкторов или удаления деструкторов для классов без виртуального деструктора. Однако, если реализация испускает такие функции, она должна использовать внешние имена, указанные в этом ABI. Если такая функция имеет внешнюю связь, она должна генерироваться, где бы она ни использовалась, в группе COMDAT, имя которой является внешним именем функции.

Другие вопросы по тегам