C++物件模型:編譯分析

sgqmax發表於2024-11-03

為了更直觀的感受到記憶體佈局,我們使用gcc的編譯選項-fdump-lang-class檢視

如下程式碼

class Base{
 public:
  Base(){}
  virtual ~Base(){}
 privte:
  int i;
};

使用如下命令編譯

g++ -O0 -std=c++11 -fdump-lang-class test.cpp

可以得到一個顯示記憶體佈局的檔案a-test.cpp.001l.class

Vtable for Base
Base::_ZTV4Base: 4 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI4Base)
16    (int (*)(...))Base::~Base
24    (int (*)(...))Base::~Base

Class Base
   size=16 align=8
   base size=12 base align=8
Base (0x0x7eafaf550420) 0
    vptr=((& Base::_ZTV4Base) + 16)

從Lippman的C++物件模型中我們知道,在含有虛擬函式的類中,編譯器都會生成一個指標,用來指向虛擬函式表

可知,類中包含一個整型變數i和一個虛擬函式表指標vptr,由於對齊,Base佔用16個位元組

虛擬函式表中包含4個指標,每個大小為8位元組
第一個為offset,儲存了虛擬函式表指標的偏移
第二個為typeinfo,指向型別資訊物件,用於RTTI
第三個為complete object destructor
第四是deleting destructor

繼承下的物件模型

如下程式碼

class Base{
 public:
  Base(){}
  virtual ~Base(){}
 private:
  int i;
};
class Derive:public Base{
 public:
  Derive(int i){}
  virtual ~Derive(){}
  virtual void get(){}
  virtual void set(){}
 private:
  int j
};
class Single:public Derive{
 public:
  Single(int i){}
  void set()override{}
  virtual void print(){}
 private:
  int k;
};

得到檔案

Vtable for Base
Base::_ZTV4Base: 4 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI4Base)
16    (int (*)(...))Base::~Base
24    (int (*)(...))Base::~Base

Class Base
   size=16 align=8
   base size=12 base align=8
Base (0x0x7a6689b50420) 0
    vptr=((& Base::_ZTV4Base) + 16)

Vtable for Derive
Derive::_ZTV6Derive: 6 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI6Derive)
16    (int (*)(...))Derive::~Derive
24    (int (*)(...))Derive::~Derive
32    (int (*)(...))Derive::get
40    (int (*)(...))Derive::set

Class Derive
   size=16 align=8
   base size=16 base align=8
Derive (0x0x7a6689a0e1a0) 0
    vptr=((& Derive::_ZTV6Derive) + 16)
Base (0x0x7a6689b508a0) 0
      primary-for Derive (0x0x7a6689a0e1a0)

Vtable for Single
Single::_ZTV6Single: 7 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI6Single)
16    (int (*)(...))Single::~Single
24    (int (*)(...))Single::~Single
32    (int (*)(...))Derive::get
40    (int (*)(...))Single::set
48    (int (*)(...))Single::print

Class Single
   size=24 align=8
   base size=20 base align=8
Single (0x0x7a6689a0e208) 0
    vptr=((& Single::_ZTV6Single) + 16)
Derive (0x0x7a6689a0e270) 0
      primary-for Single (0x0x7a6689a0e208)
Base (0x0x7a6689b50d80) 0
        primary-for Derive (0x0x7a6689a0e270)

單繼承場景,派生類只有一個虛擬函式表,複製於基類,同時將override的虛擬函式進行覆蓋,派生類的虛擬函式也追加到表尾
派生類新增的非靜態成員變數,也會追加到基類的成員變數後面
靜態成員變數儲存在.段,為堆區

多繼承下的物件模型

非菱形繼承

class Base_A{
 public:
  Base_A(int i){}
  virtual ~Base_A(){}
  int get(){}
  virtual void set(){}
  static void countA(){}
 private:
  int i;
  static int ii;
};

class Base_B{
 public:
  Base_B(int i){}
  virtual ~Base_B(){}
  int get(){}
  virtual void set(){}
  virtual void add(){}
  static void countA(){}
 private:
  int j;
  static int jj;
};

class Derive:public Base_A,public Base_B{
 public:
  Derive(int d){}
  void add()override{}
  void set()override{}
  virtual void print(){}
 private:
  int k;
};

生成如下

Vtable for Base_A
Base_A::_ZTV6Base_A: 5 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI6Base_A)
16    (int (*)(...))Base_A::~Base_A
24    (int (*)(...))Base_A::~Base_A
32    (int (*)(...))Base_A::set

Class Base_A
   size=16 align=8
   base size=12 base align=8
Base_A (0x0x7983aa950420) 0
    vptr=((& Base_A::_ZTV6Base_A) + 16)

Vtable for Base_B
Base_B::_ZTV6Base_B: 6 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI6Base_B)
16    (int (*)(...))Base_B::~Base_B
24    (int (*)(...))Base_B::~Base_B
32    (int (*)(...))Base_B::set
40    (int (*)(...))Base_B::add

Class Base_B
   size=16 align=8
   base size=12 base align=8
Base_B (0x0x7983aa9509c0) 0
    vptr=((& Base_B::_ZTV6Base_B) + 16)

Vtable for Derive
Derive::_ZTV6Derive: 13 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI6Derive)
16    (int (*)(...))Derive::~Derive
24    (int (*)(...))Derive::~Derive
32    (int (*)(...))Derive::set
40    (int (*)(...))Derive::add
48    (int (*)(...))Derive::print
56    (int (*)(...))-16
64    (int (*)(...))(& _ZTI6Derive)
72    (int (*)(...))Derive::_ZThn16_N6DeriveD1Ev
80    (int (*)(...))Derive::_ZThn16_N6DeriveD0Ev
88    (int (*)(...))Derive::_ZThn16_N6Derive3setEv
96    (int (*)(...))Derive::_ZThn16_N6Derive3addEv

Class Derive
   size=32 align=8
   base size=32 base align=8
Derive (0x0x7983aa963930) 0
    vptr=((& Derive::_ZTV6Derive) + 16)
Base_A (0x0x7983aa950f00) 0
      primary-for Derive (0x0x7983aa963930)
Base_B (0x0x7983aa950f60) 16
      vptr=((& Derive::_ZTV6Derive) + 72)

可以看出派生類中存在兩個虛擬函式表指標vptr

enable_shared_from_this特性

表現為讓例項擁有一個弱引用

如下程式碼

class Derive:public Base,
             public std::enable_shared_from_this<Derive{
 public:
  Derive(int i){}
  void set()override;
  virtual void print();
 private:
  int i;
};

可以看出,記憶體有所增大,因為enable_shared_from_this繼承多佔用了16位元組
從std::weak_ptr實現中可知,實際上儲存了兩個指標

enable_shared_from_this不影響Derive的虛擬函式表內容

相關文章