为什么我的程序集输出中有两个析构函数实现?

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

我的 .o 文件的 objdump 显示我有两个不同的析构函数用于相同的 class。为什么?

Disassembly of section .text._ZN1AD0Ev:

0000000000000000 <_ZN1AD0Ev>:
   0:   53                      push   %rbx
   1:   be 00 00 00 00          mov    [=10=]x0,%esi
   6:   48 89 fb                mov    %rdi,%rbx
   9:   48 c7 07 00 00 00 00    movq   [=10=]x0,(%rdi)
  10:   ba 2c 00 00 00          mov    [=10=]x2c,%edx
  15:   bf 00 00 00 00          mov    [=10=]x0,%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    [=10=]x8,%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   [=10=]x0,(%rdi)
   7:   ba 2c 00 00 00          mov    [=10=]x2c,%edx
   c:   be 00 00 00 00          mov    [=10=]x0,%esi
  11:   bf 00 00 00 00          mov    [=10=]x0,%edi
  16:   e9 00 00 00 00          jmpq   1b <_ZN1AD1Ev+0x1b>

这些是导致生成此代码的头文件中的 classes:

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

许多编译器为一个 class 生成两个不同的析构函数:一个用于销毁动态分配的对象,另一个 - 用于销毁非动态对象(静态对象、局部对象、基础子对象或成员子对象) ).前者从内部调用 operator delete,后者不调用。一些编译器通过向一个析构函数添加一个隐藏参数来实现这一点(旧版本的 GCC 这样做,MSVC++ 那样做),一些编译器只是生成两个单独的析构函数(新版本的 GCC 这样做)。

从析构函数内部调用 operator delete 的需要来自 C++ 规范,它说应该选择正确的 operator delete "as if" 它是从内部查找的(可能是virtual) 最派生对象的析构函数。因此,可以作为 static 成员函数实现的 operator delete 应该表现得像 virtual 函数一样。

大多数实现都实现了这个要求"literally":它们不仅从析构函数内部查找正确的operator delete,而且实际上从那里调用它。

当然,operator delete 仅需从最派生对象的析构函数中调用,并且仅当该对象是动态分配的。这就是隐藏参数(或两个版本的析构函数)发挥作用的地方。

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

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)。

最后说明一下这两个析构函数的区别:

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()

此外,只有当您的 class 具有虚拟析构函数时才会发生这种情况:

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.