C++ dtor 和虚表指针的一个讨论


#include <cstdio>

struct Base {
  int vb;
  virtual ~Base() {
    puts("Base::~Base");
    test(0);
  }
  virtual void test(int) { puts("Base::test"); }
  // error!
  // virtual void test(int) = 0;
};

struct Derived : public Base {
  int vd;
  ~Derived() override {
    puts("Derived::~Derived");
    test(0);
  }
  void test(int) override { puts("Derived::test"); }
};

int main() {
  Base *a = new Derived();
  a->test(1);
  puts("");
  a->~Base();
  puts("");
  a->test(1);
  return 0;
}

输出:

Derived::test

Derived::~Derived
Derived::test
Base::~Base
Base::test

Base::test

Base 析构的时候调用 test 的流程:

Derived::~Derived(Derived *this) {
  this->vptr = vtable_for_Derived+16; // *(void *)this = offset_vtable;
  // actual user defined Derived::~Derived
  Base::Base(*this);
}

Base::~Base(Base *this) {
  this->vptr = vtable_for_Base+16;
  // actual user defined Base::~Base
}

vptr 是在 dtor 开始的时候设置的,所以 Base::test(int) 不能是纯虚函数。

多重继承的情况类似,假设 Derived 继承 Base1, Base2, Base3Base* 类中有一个 int64_t 成员变量:

Derived 内存布局:
+-------+------+-+
| Base1 | vptr |0|
|       +------+-+
|       | var  |1|
+-------+------+-+
| Base2 | vptr |2|
|       +------+-+
|       | var  |3|
+-------+------+-+
| Base3 | vptr |4|
|       +------+-+
|       | var  |5|
+-------+------+-+
|vptr          |6|
+--------------+-+
|var           |7|
+--------------+-+
Derived::~Derived(Derived *this) {
  // vptr_Base1 = 0;
  // vptr_Base2 = 2 * 8;
  // vptr_Base3 = 4 * 8;
  this->vptr_Base1 = vtable_for_Derived+16;
  this->vptr_Base2 = vtable_for_Derived_thunk_1+16;
  this->vptr_Base3 = vtable_for_Derived_thunk_2+16;

  // actual user defined Derived::~Derived

  Base3::~Base3(this + offset_vptr_Base3);
  Base2::~Base2(this + offset_vptr_Base2);
  Base1::~Base1(this + offset_vptr_Base1);
}

Base2, Base3 对应的 vptr 指向各自的 virtual thunk ,其中的函数只是实际函数的跳板:

.data:3C48                 public _ZTV7Derived ; weak
.data:3C48 ; `vtable for'Derived
.data:3C48 _ZTV7Derived    dq 0                    ; offset to this
.data:3C50                 dq offset _ZTI7Derived  ; `typeinfo for'Derived
.data:3C58 off_3C58        dq offset _ZN7DerivedD2Ev
.data:3C58                                         ; DATA XREF: Derived::~Derived()+C↑o
.data:3C58                                         ; Derived::Derived(void)+38↑o
.data:3C58                                         ; Derived::~Derived()
.data:3C60                 dq offset _ZN7DerivedD0Ev ; Derived::~Derived()
.data:3C68                 dq offset _ZN7Derived4testEi ; Derived::test(int)
.data:3C70                 dq -16                  ; offset to this
.data:3C78                 dq offset _ZTI7Derived  ; `typeinfo for'Derived
.data:3C80 off_3C80        dq offset _ZThn16_N7DerivedD1Ev
.data:3C80                                         ; DATA XREF: Derived::~Derived()+1A↑o
.data:3C80                                         ; Derived::Derived(void)+46↑o
.data:3C80                                         ; `non-virtual thunk to'Derived::~Derived()
.data:3C88                 dq offset _ZThn16_N7DerivedD0Ev ; `non-virtual thunk to'Derived::~Derived()
.data:3C90                 dq offset _ZThn16_N7Derived4testEi ; `non-virtual thunk to'Derived::test(int)
.data:3C98                 dq -32                  ; offset to this
.data:3CA0                 dq offset _ZTI7Derived  ; `typeinfo for'Derived
.data:3CA8 off_3CA8        dq offset _ZThn32_N7DerivedD1Ev
.data:3CA8                                         ; DATA XREF: Derived::~Derived()+29↑o
.data:3CA8                                         ; Derived::Derived(void)+55↑o
.data:3CA8                                         ; `non-virtual thunk to'Derived::~Derived()
.data:3CB0                 dq offset _ZThn32_N7DerivedD0Ev ; `non-virtual thunk to'Derived::~Derived()
.data:3CB8                 dq offset _ZThn32_N7Derived4testEi ; `non-virtual thunk to'Derived::test(int)