日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當(dāng)前位置:首頁(yè) > 單片機(jī) > 程序喵大人
[導(dǎo)讀]我們都知道C++多態(tài)是通過(guò)虛函數(shù)表來(lái)實(shí)現(xiàn)的,那具體是什么樣的大家清楚嗎?開篇依舊提出來(lái)幾個(gè)問(wèn)題: 普通類對(duì)象是什么布局? 帶虛函數(shù)的類對(duì)象是什么布局? 單繼承下不含有覆蓋函數(shù)的類對(duì)象是什么布局? 單繼承下含有覆蓋函數(shù)的類對(duì)象是什么布局? 多繼承下不

我們都知道C++多態(tài)是通過(guò)虛函數(shù)表來(lái)實(shí)現(xiàn)的,那具體是什么樣的大家清楚嗎?開篇依舊提出來(lái)幾個(gè)問(wèn)題:

  • 普通類對(duì)象是什么布局?

  • 帶虛函數(shù)的類對(duì)象是什么布局?

  • 單繼承下不含有覆蓋函數(shù)的類對(duì)象是什么布局?

  • 單繼承下含有覆蓋函數(shù)的類對(duì)象是什么布局?

  • 多繼承下不含有覆蓋函數(shù)的類對(duì)象是什么布局?

  • 多繼承下含有覆蓋函數(shù)的類對(duì)象的是什么布局?

  • 多繼承中不同的繼承順序產(chǎn)生的類對(duì)象布局相同嗎?

  • 虛繼承的類對(duì)象是什么布局?

  • 菱形繼承下類對(duì)象是什么布局?

  • 為什么要引入虛繼承?

  • 為什么虛函數(shù)表中有兩個(gè)析構(gòu)函數(shù)?

  • 為什么構(gòu)造函數(shù)不能是虛函數(shù)?

  • 為什么基類析構(gòu)函數(shù)需要是虛函數(shù)?

要回答上述問(wèn)題我們首先需要了解什么是多態(tài)。

什么是多態(tài)?

多態(tài)可以分為編譯時(shí)多態(tài)和運(yùn)行時(shí)多態(tài)。

  • 編譯時(shí)多態(tài):基于模板和函數(shù)重載方式,在編譯時(shí)就已經(jīng)確定對(duì)象的行為,也稱為靜態(tài)綁定。

  • 運(yùn)行時(shí)多態(tài):面向?qū)ο蟮囊淮筇厣?,通過(guò)繼承方式使得程序在運(yùn)行時(shí)才會(huì)確定相應(yīng)調(diào)用的方法,也稱為動(dòng)態(tài)綁定,它的實(shí)現(xiàn)主要是依賴于傳說(shuō)中的虛函數(shù)表。

如何查看對(duì)象的布局?

在gcc中可以使用如下命令查看對(duì)象布局:

g++ -fdump-class-hierarchy model.cc后查看生成的文件

在clang中可以使用如下命令:

clang -Xclang -fdump-record-layouts -stdlib=libc++ -c model.cc// 查看對(duì)象布局clang -Xclang -fdump-vtable-layouts -stdlib=libc++ -c model.cc// 查看虛函數(shù)表布局

上面兩種方式其實(shí)足夠了,也可以使用gdb來(lái)查看內(nèi)存布局,這里可以看文末相關(guān)參考資料。本文都是使用clang來(lái)查看的對(duì)象布局。

接下來(lái)讓我們一起來(lái)探秘下各種繼承條件下類對(duì)象的布局情況吧~

普通類對(duì)象的布局

下代碼:

struct Base { Base() = default; ~Base() = default;  void Func() {}
int a; int b;};
int main() { Base a; return 0;}
// 使用clang -Xclang -fdump-record-layouts -stdlib=libc++ -c model.cc查看

輸出如下:

*** Dumping AST Record Layout 0 | struct Base 0 | int a 4 | int b | [sizeof=8, dsize=8, align=4, | nvsize=8, nvalign=4]
*** Dumping IRgen Record Layout

畫出圖如下:

從結(jié)果中可以看見,這個(gè)普通結(jié)構(gòu)體Base的大小為8字節(jié),a占4個(gè)字節(jié),b占4個(gè)字節(jié)。

帶虛函數(shù)的類對(duì)象布局
struct Base { Base() = default; virtual ~Base() = default;  void FuncA() {}
virtual void FuncB() { printf("FuncB\n"); }
int a; int b;};
int main() { Base a; return 0;}
// 這里可以查看對(duì)象的布局和相應(yīng)虛函數(shù)表的布局clang -Xclang -fdump-record-layouts -stdlib=libc++ -c model.ccclang -Xclang -fdump-vtable-layouts -stdlib=libc++ -c model.cc

對(duì)象布局如下:

*** Dumping AST Record Layout 0 | struct Base 0 | (Base vtable pointer) 8 | int a 12 | int b | [sizeof=16, dsize=16, align=8, | nvsize=16, nvalign=8]
*** Dumping IRgen Record Layout

這個(gè)含有虛函數(shù)的結(jié)構(gòu)體大小為16,在對(duì)象的頭部,前8個(gè)字節(jié)是虛函數(shù)表的指針,指向虛函數(shù)的相應(yīng)函數(shù)指針地址,a占4個(gè)字節(jié),b占4個(gè)字節(jié),總大小為16。

虛函數(shù)表布局:

Vtable for 'Base' (5 entries). 0 | offset_to_top (0) 1 | Base RTTI -- (Base, 0) vtable address -- 2 | Base::~Base() [complete] 3 | Base::~Base() [deleting] 4 | void Base::FuncB()

畫出對(duì)象布局圖如下:

我們來(lái)探秘下傳說(shuō)中的虛函數(shù)表:

offset_to_top(0)表示當(dāng)前這個(gè)虛函數(shù)表地址距離對(duì)象頂部地址的偏移量,因?yàn)閷?duì)象的頭部就是虛函數(shù)表的指針,所以偏移量為0。

RTTI指針指向存儲(chǔ)運(yùn)行時(shí)類型信息(type_info)的地址,用于運(yùn)行時(shí)類型識(shí)別,用于typeid和dynamic_cast。

RTTI下面就是虛函數(shù)表指針真正指向的地址啦,存儲(chǔ)了類里面所有的虛函數(shù),至于這里為什么會(huì)有兩個(gè)析構(gòu)函數(shù),大家可以先關(guān)注對(duì)象的布局,最下面會(huì)介紹。

單繼承下不含有覆蓋函數(shù)的類對(duì)象的布局

struct Base { Base() = default; virtual ~Base() = default;  void FuncA() {}
virtual void FuncB() { printf("Base FuncB\n"); }
int a; int b;};
struct Derive : public Base{};
int main() { Base a; Derive d; return 0;}

子類對(duì)象布局:

*** Dumping AST Record Layout 0 | struct Derive 0 | struct Base (primary base) 0 | (Base vtable pointer) 8 | int a 12 | int b | [sizeof=16, dsize=16, align=8, | nvsize=16, nvalign=8]
*** Dumping IRgen Record Layout

和上面相同,這個(gè)含有虛函數(shù)的結(jié)構(gòu)體大小為16,在對(duì)象的頭部,前8個(gè)字節(jié)是虛函數(shù)表的指針,指向虛函數(shù)的相應(yīng)函數(shù)指針地址,a占4個(gè)字節(jié),b占4個(gè)字節(jié),總大小為16。

子類虛函數(shù)表布局:

Vtable for 'Derive' (5 entries). 0 | offset_to_top (0) 1 | Derive RTTI -- (Base, 0) vtable address -- -- (Derive, 0) vtable address -- 2 | Derive::~Derive() [complete] 3 | Derive::~Derive() [deleting] 4 | void Base::FuncB()

畫圖如下:

這個(gè)和上面也是相同的,注意下虛函數(shù)表這里的FuncB函數(shù),還是Base類中的FuncB,因?yàn)樵谧宇愔袥]有重寫這個(gè)函數(shù),那么如果子類重寫這個(gè)函數(shù)后對(duì)象布局是什么樣的,請(qǐng)繼續(xù)往下看哈。

單繼承下含有覆蓋函數(shù)的類對(duì)象的布局

struct Base { Base() = default; virtual ~Base() = default;  void FuncA() {}
virtual void FuncB() { printf("Base FuncB\n"); }
int a; int b;};
struct Derive : public Base{ void FuncB() override { printf("Derive FuncB \n"); }};
int main() { Base a; Derive d; return 0;}

子類對(duì)象布局:

*** Dumping AST Record Layout 0 | struct Derive 0 | struct Base (primary base) 0 | (Base vtable pointer) 8 | int a 12 | int b | [sizeof=16, dsize=16, align=8, | nvsize=16, nvalign=8]
*** Dumping IRgen Record Layout

依舊和上面相同,這個(gè)含有虛函數(shù)的結(jié)構(gòu)體大小為16,在對(duì)象的頭部,前8個(gè)字節(jié)是虛函數(shù)表的指針,指向虛函數(shù)的相應(yīng)函數(shù)指針地址,a占4個(gè)字節(jié),b占4個(gè)字節(jié),總大小為16。

子類虛函數(shù)表布局:

Vtable for 'Derive' (5 entries). 0 | offset_to_top (0) 1 | Derive RTTI -- (Base, 0) vtable address -- -- (Derive, 0) vtable address -- 2 | Derive::~Derive() [complete] 3 | Derive::~Derive() [deleting] 4 | void Derive::FuncB()

注意這里虛函數(shù)表中的FuncB函數(shù)已經(jīng)是Derive中的FuncB啦,因?yàn)樵谧宇愔兄貙懥烁割惖倪@個(gè)函數(shù)。

再注意這里的RTTI中有了兩項(xiàng),表示Base和Derive的虛表地址是相同的,Base類里的虛函數(shù)和Derive類里的虛函數(shù)都在這個(gè)鏈條下,這里可以繼續(xù)關(guān)注下面多繼承的情況,看看有何不同。

多繼承下不含有覆蓋函數(shù)的類對(duì)象的布局

struct BaseA { BaseA() = default; virtual ~BaseA() = default;  void FuncA() {}
virtual void FuncB() { printf("BaseA FuncB\n"); }
int a; int b;};
struct BaseB { BaseB() = default; virtual ~BaseB() = default; void FuncA() {}
virtual void FuncC() { printf("BaseB FuncC\n"); }
int a; int b;};
struct Derive : public BaseA, public BaseB{};
int main() { BaseA a; Derive d; return 0;}

類對(duì)象布局:

*** Dumping AST Record Layout 0 | struct Derive 0 | struct BaseA (primary base) 0 | (BaseA vtable pointer) 8 | int a 12 | int b 16 | struct BaseB (base) 16 | (BaseB vtable pointer) 24 | int a 28 | int b | [sizeof=32, dsize=32, align=8, | nvsize=32, nvalign=8]

Derive大小為32,注意這里有了兩個(gè)虛表指針,因?yàn)镈erive是多繼承,一般情況下繼承了幾個(gè)帶有虛函數(shù)的類,對(duì)象布局中就有幾個(gè)虛表指針,并且子類也會(huì)繼承基類的數(shù)據(jù),一般來(lái)說(shuō),不考慮內(nèi)存對(duì)齊的話,子類(繼承父類)的大小=子類(不繼承父類)的大小+所有父類的大小

虛函數(shù)表布局:

Vtable for 'Derive' (10 entries). 0 | offset_to_top (0) 1 | Derive RTTI -- (BaseA, 0) vtable address -- -- (Derive, 0) vtable address -- 2 | Derive::~Derive() [complete] 3 | Derive::~Derive() [deleting] 4 | void BaseA::FuncB() 5 | offset_to_top (-16) 6 | Derive RTTI -- (BaseB, 16) vtable address -- 7 | Derive::~Derive() [complete] [this adjustment: -16 non-virtual] 8 | Derive::~Derive() [deleting] [this adjustment: -16 non-virtual] 9 | void BaseB::FuncC()

可畫出對(duì)象布局圖如下:

offset_to_top(0)表示當(dāng)前這個(gè)虛函數(shù)表(BaseA,Derive)地址距離對(duì)象頂部地址的偏移量,因?yàn)閷?duì)象的頭部就是虛函數(shù)表的指針,所以偏移量為0。

再注意這里的RTTI中有了兩項(xiàng),表示BaseA和Derive的虛表地址是相同的,BaseA類里的虛函數(shù)和Derive類里的虛函數(shù)都在這個(gè)鏈條下,截至到offset_to_top(-16)之前都是BaseA和Derive的虛函數(shù)表。

offset_to_top(-16)表示當(dāng)前這個(gè)虛函數(shù)表(BaseB)地址距離對(duì)象頂部地址的偏移量,因?yàn)閷?duì)象的頭部就是虛函數(shù)表的指針,所以偏移量為-16,這里用于this指針偏移,下一小節(jié)會(huì)介紹。

注意下后面的這個(gè)RTTI:只有一項(xiàng),表示BaseB的虛函數(shù)表,后面也有兩個(gè)虛析構(gòu)函數(shù),為什么有四個(gè)Derive類的析構(gòu)函數(shù)呢,又是怎么調(diào)用呢,請(qǐng)繼續(xù)往下看~

多繼承下含有覆蓋函數(shù)的類對(duì)象的布局

struct BaseA { BaseA() = default; virtual ~BaseA() = default;  void FuncA() {}
virtual void FuncB() { printf("BaseA FuncB\n"); }
int a; int b;};
struct BaseB { BaseB() = default; virtual ~BaseB() = default; void FuncA() {}
virtual void FuncC() { printf("BaseB FuncC\n"); }
int a; int b;};
struct Derive : public BaseA, public BaseB{ void FuncB() override { printf("Derive FuncB \n"); }
void FuncC() override { printf("Derive FuncC \n"); }};
int main() { BaseA a; Derive d; return 0;}

對(duì)象布局:

*** Dumping AST Record Layout 0 | struct Derive 0 | struct BaseA (primary base) 0 | (BaseA vtable pointer) 8 | int a 12 | int b 16 | struct BaseB (base) 16 | (BaseB vtable pointer) 24 | int a 28 | int b | [sizeof=32, dsize=32, align=8, | nvsize=32, nvalign=8]
*** Dumping IRgen Record Layout

類大小仍然是32,和上面一樣。

虛函數(shù)表布局:

Vtable for 'Derive' (11 entries). 0 | offset_to_top (0) 1 | Derive RTTI -- (BaseA, 0) vtable address -- -- (Derive, 0) vtable address -- 2 | Derive::~Derive() [complete] 3 | Derive::~Derive() [deleting] 4 | void Derive::FuncB() 5 | void Derive::FuncC() 6 | offset_to_top (-16) 7 | Derive RTTI -- (BaseB, 16) vtable address -- 8 | Derive::~Derive() [complete] [this adjustment: -16 non-virtual] 9 | Derive::~Derive() [deleting] [this adjustment: -16 non-virtual] 10 | void Derive::FuncC() [this adjustment: -16 non-virtual]

offset_to_top(0)表示當(dāng)前這個(gè)虛函數(shù)表(BaseA,Derive)地址距離對(duì)象頂部地址的偏移量,因?yàn)閷?duì)象的頭部就是虛函數(shù)表的指針,所以偏移量為0。

再注意這里的RTTI中有了兩項(xiàng),表示BaseA和Derive的虛表地址是相同的,BaseA類里的虛函數(shù)和Derive類里的虛函數(shù)都在這個(gè)鏈條下,截至到offset_to_top(-16)之前都是BaseA和Derive的虛函數(shù)表。

offset_to_top(-16)表示當(dāng)前這個(gè)虛函數(shù)表(BaseB)地址距離對(duì)象頂部地址的偏移量,因?yàn)閷?duì)象的頭部就是虛函數(shù)表的指針,所以偏移量為-16。當(dāng)基類BaseB的引用或指針base實(shí)際接受的是Derive類型的對(duì)象,執(zhí)行base->FuncC()時(shí)候,由于FuncC()已經(jīng)被重寫,而此時(shí)的this指針指向的是BaseB類型的對(duì)象,需要對(duì)this指針進(jìn)行調(diào)整,就是offset_to_top(-16),所以this指針向上調(diào)整了16字節(jié),之后調(diào)用FuncC(),就調(diào)用到了被重寫后Derive虛函數(shù)表中的FuncC()函數(shù)。這些帶adjustment標(biāo)記的函數(shù)都是需要進(jìn)行指針調(diào)整的。至于上面所說(shuō)的這里虛函數(shù)是怎么調(diào)用的,估計(jì)您也明白了吧~

多重繼承不同的繼承順序?qū)е碌念悓?duì)象的布局相同嗎?

struct BaseA { BaseA() = default; virtual ~BaseA() = default;  void FuncA() {}
virtual void FuncB() { printf("BaseA FuncB\n"); }
int a; int b;};
struct BaseB { BaseB() = default; virtual ~BaseB() = default; void FuncA() {}
virtual void FuncC() { printf("BaseB FuncC\n"); }
int a; int b;};
struct Derive : public BaseB, public BaseA{ void FuncB() override { printf("Derive FuncB \n"); }
void FuncC() override { printf("Derive FuncC \n"); }};
int main() { BaseA a; Derive d; return 0;}

對(duì)象布局:

*** Dumping AST Record Layout 0 | struct Derive 0 | struct BaseB (primary base) 0 | (BaseB vtable pointer) 8 | int a 12 | int b 16 | struct BaseA (base) 16 | (BaseA vtable pointer) 24 | int a 28 | int b | [sizeof=32, dsize=32, align=8, | nvsize=32, nvalign=8]
*** Dumping IRgen Record Layout

這里可見,對(duì)象布局和上面的不相同啦,BaseB的虛函數(shù)表指針和數(shù)據(jù)在上面,BaseA的虛函數(shù)表指針和數(shù)據(jù)在下面,以A,B的順序繼承,對(duì)象的布局就是A在上B在下,以B,A的順序繼承,對(duì)象的布局就是B在上A在下。

虛函數(shù)表布局:

Vtable for 'Derive' (11 entries). 0 | offset_to_top (0) 1 | Derive RTTI -- (BaseB, 0) vtable address -- -- (Derive, 0) vtable address -- 2 | Derive::~Derive() [complete] 3 | Derive::~Derive() [deleting] 4 | void Derive::FuncC() 5 | void Derive::FuncB() 6 | offset_to_top (-16) 7 | Derive RTTI -- (BaseA, 16) vtable address -- 8 | Derive::~Derive() [complete] [this adjustment: -16 non-virtual] 9 | Derive::~Derive() [deleting] [this adjustment: -16 non-virtual] 10 | void Derive::FuncB() [this adjustment: -16 non-virtual]

對(duì)象布局圖如下:

虛函數(shù)表的布局也有所不同,BaseB和Derive共用一個(gè)虛表地址,在整個(gè)虛表布局的上方,而布局的下半部分是BaseA的虛表,可見繼承順序不同,子類的虛表布局也有所不同。

虛繼承的布局

struct Base { Base() = default; virtual ~Base() = default;  void FuncA() {}
virtual void FuncB() { printf("BaseA FuncB\n"); }
int a; int b;};
struct Derive : virtual public Base{ void FuncB() override { printf("Derive FuncB \n"); }};
int main() { Base a; Derive d; return 0;}

對(duì)象布局:

*** Dumping AST Record Layout 0 | struct Derive 0 | (Derive vtable pointer) 8 | struct Base (virtual base) 8 | (Base vtable pointer) 16 | int a 20 | int b | [sizeof=24, dsize=24, align=8, | nvsize=8, nvalign=8]
*** Dumping IRgen Record Layout

虛繼承下,這里的對(duì)象布局和普通單繼承有所不同,普通單繼承下子類和基類共用一個(gè)虛表地址,而在虛繼承下,子類和虛基類分別有一個(gè)虛表地址的指針,兩個(gè)指針大小總和為16,再加上a和b的大小8,為24。

虛函數(shù)表:

Vtable for 'Derive' (13 entries). 0 | vbase_offset (8) 1 | offset_to_top (0) 2 | Derive RTTI -- (Derive, 0) vtable address -- 3 | void Derive::FuncB() 4 | Derive::~Derive() [complete] 5 | Derive::~Derive() [deleting] 6 | vcall_offset (-8) 7 | vcall_offset (-8) 8 | offset_to_top (-8) 9 | Derive RTTI -- (Base, 8) vtable address -- 10 | Derive::~Derive() [complete] [this adjustment: 0 non-virtual, -24 vcall offset offset] 11 | Derive::~Derive() [deleting] [this adjustment: 0 non-virtual, -24 vcall offset offset] 12 | void Derive::FuncB() [this adjustment: 0 non-virtual, -32 vcall offset offset]

對(duì)象布局圖如下:

vbase_offset(8)對(duì)象在對(duì)象布局中與指向虛基類虛函數(shù)表的指針地址的偏移量

vcall_offset(-8)當(dāng)虛基類Base的引用或指針base實(shí)際接受的是Derive類型的對(duì)象,執(zhí)行base->FuncB()時(shí)候,由于FuncB()已經(jīng)被重寫,而此時(shí)的this指針指向的是Base類型的對(duì)象,需要對(duì)this指針進(jìn)行調(diào)整,就是vcall_offset(-8),所以this指針向上調(diào)整了8字節(jié),之后調(diào)用FuncB(),就調(diào)用到了被重寫后的FuncB()函數(shù)。

虛繼承帶未覆蓋函數(shù)的對(duì)象布局

struct Base { Base() = default; virtual ~Base() = default;  void FuncA() {}
virtual void FuncB() { printf("Base FuncB\n"); }
virtual void FuncC() { printf("Base FuncC\n"); }
int a; int b;};
struct Derive : virtual public Base{ void FuncB() override { printf("Derive FuncB \n"); }};
int main() { Base a; Derive d; return 0;}

對(duì)象布局:

*** Dumping AST Record Layout 0 | struct Derive 0 | (Derive vtable pointer) 8 | struct Base (virtual base) 8 | (Base vtable pointer) 16 | int a 20 | int b | [sizeof=24, dsize=24, align=8, | nvsize=8, nvalign=8]
*** Dumping IRgen Record Layout

和上面虛繼承情況下相同,普通單繼承下子類和基類共用一個(gè)虛表地址,而在虛繼承下,子類和虛基類分別有一個(gè)虛表地址的指針,兩個(gè)指針大小總和為16,再加上a和b的大小8,為24。

虛函數(shù)表布局:

Vtable for 'Derive' (15 entries). 0 | vbase_offset (8) 1 | offset_to_top (0) 2 | Derive RTTI -- (Derive, 0) vtable address -- 3 | void Derive::FuncB() 4 | Derive::~Derive() [complete] 5 | Derive::~Derive() [deleting] 6 | vcall_offset (0) 7 | vcall_offset (-8) 8 | vcall_offset (-8) 9 | offset_to_top (-8) 10 | Derive RTTI -- (Base, 8) vtable address -- 11 | Derive::~Derive() [complete] [this adjustment: 0 non-virtual, -24 vcall offset offset] 12 | Derive::~Derive() [deleting] [this adjustment: 0 non-virtual, -24 vcall offset offset] 13 | void Derive::FuncB() [this adjustment: 0 non-virtual, -32 vcall offset offset] 14 | void Base::FuncC()

對(duì)象布局圖如下:

vbase_offset(8)對(duì)象在對(duì)象布局中與指向虛基類虛函數(shù)表的指針地址的偏移量

vcall_offset(-8)當(dāng)虛基類Base的引用或指針base實(shí)際接受的是Derive類型的對(duì)象,執(zhí)行base->FuncB()時(shí)候,由于FuncB()已經(jīng)被重寫,而此時(shí)的this指針指向的是Base類型的對(duì)象,需要對(duì)this指針進(jìn)行調(diào)整,就是vcall_offset(-8),所以this指針向上調(diào)整了8字節(jié),之后調(diào)用FuncB(),就調(diào)用到了被重寫后的FuncB()函數(shù)。

vcall_offset(0)當(dāng)Base的引用或指針base實(shí)際接受的是Derive類型的對(duì)象,執(zhí)行base->FuncC()時(shí)候,由于FuncC()沒有被重寫,所以不需要對(duì)this指針進(jìn)行調(diào)整,就是vcall_offset(0),之后調(diào)用FuncC()。

菱形繼承下類對(duì)象的布局

struct Base { Base() = default; virtual ~Base() = default;  void FuncA() {}
virtual void FuncB() { printf("BaseA FuncB\n"); }
int a; int b;};
struct BaseA : virtual public Base { BaseA() = default; virtual ~BaseA() = default; void FuncA() {}
virtual void FuncB() { printf("BaseA FuncB\n"); }
int a; int b;};
struct BaseB : virtual public Base { BaseB() = default; virtual ~BaseB() = default; void FuncA() {}
virtual void FuncC() { printf("BaseB FuncC\n"); }
int a; int b;};
struct Derive : public BaseB, public BaseA{ void FuncB() override { printf("Derive FuncB \n"); }
void FuncC() override { printf("Derive FuncC \n"); }};
int main() { BaseA a; Derive d; return 0;}

類對(duì)象布局:

*** Dumping AST Record Layout 0 | struct Derive 0 | struct BaseB (primary base) 0 | (BaseB vtable pointer) 8 | int a 12 | int b 16 | struct BaseA (base) 16 | (BaseA vtable pointer) 24 | int a 28 | int b 32 | struct Base (virtual base) 32 | (Base vtable pointer) 40 | int a 44 | int b | [sizeof=48, dsize=48, align=8, | nvsize=32, nvalign=8]
*** Dumping IRgen Record Layout

大小為48,這里不用做過(guò)多介紹啦,相信您已經(jīng)知道了吧。

虛函數(shù)表:

Vtable for 'Derive' (20 entries). 0 | vbase_offset (32) 1 | offset_to_top (0) 2 | Derive RTTI -- (BaseB, 0) vtable address -- -- (Derive, 0) vtable address -- 3 | Derive::~Derive() [complete] 4 | Derive::~Derive() [deleting] 5 | void Derive::FuncC() 6 | void Derive::FuncB() 7 | vbase_offset (16) 8 | offset_to_top (-16) 9 | Derive RTTI -- (BaseA, 16) vtable address -- 10 | Derive::~Derive() [complete] [this adjustment: -16 non-virtual] 11 | Derive::~Derive() [deleting] [this adjustment: -16 non-virtual] 12 | void Derive::FuncB() [this adjustment: -16 non-virtual] 13 | vcall_offset (-32) 14 | vcall_offset (-32) 15 | offset_to_top (-32) 16 | Derive RTTI -- (Base, 32) vtable address -- 17 | Derive::~Derive() [complete] [this adjustment: 0 non-virtual, -24 vcall offset offset] 18 | Derive::~Derive() [deleting] [this adjustment: 0 non-virtual, -24 vcall offset offset] 19 | void Derive::FuncB() [this adjustment: 0 non-virtual, -32 vcall offset offset]

對(duì)象布局圖如下:

vbase_offset (32)

vbase_offset (16)對(duì)象在對(duì)象布局中與指向虛基類虛函數(shù)表的指針地址的偏移量

offset_to_top (0)

offset_to_top (-16)

offset_to_top (-32)指向虛函數(shù)表的地址與對(duì)象頂部地址的偏移量。

vcall_offset(-32)當(dāng)虛基類Base的引用或指針base實(shí)際接受的是Derive類型的對(duì)象,執(zhí)行base->FuncB()時(shí)候,由于FuncB()已經(jīng)被重寫,而此時(shí)的this指針指向的是Base類型的對(duì)象,需要對(duì)this指針進(jìn)行調(diào)整,就是vcall_offset(-32),所以this指針向上調(diào)整了32字節(jié),之后調(diào)用FuncB(),就調(diào)用到了被重寫后的FuncB()函數(shù)。

為什么要虛繼承?

如圖:

非虛繼承時(shí),顯然D會(huì)繼承兩次A,內(nèi)部就會(huì)存儲(chǔ)兩份A的數(shù)據(jù)浪費(fèi)空間,而且還有二義性,D調(diào)用A的方法時(shí),由于有兩個(gè)A,究竟時(shí)調(diào)用哪個(gè)A的方法呢,編譯器也不知道,就會(huì)報(bào)錯(cuò),所以有了虛繼承,解決了空間浪費(fèi)以及二義性問(wèn)題。在虛擬繼承下,只有一個(gè)共享的基類子對(duì)象被繼承,而無(wú)論該基類在派生層次中出現(xiàn)多少次。共享的基類子對(duì)象被稱為虛基類。在虛繼承下,基類子對(duì)象的復(fù)制及由此而引起的二義性都被消除了。

為什么虛函數(shù)表中有兩個(gè)析構(gòu)函數(shù)?

前面的代碼輸出中我們可以看到虛函數(shù)表中有兩個(gè)析構(gòu)函數(shù),一個(gè)標(biāo)志為deleting,一個(gè)標(biāo)志為complete,因?yàn)閷?duì)象有兩種構(gòu)造方式,棧構(gòu)造和堆構(gòu)造,所以對(duì)應(yīng)的實(shí)現(xiàn)上,對(duì)象也有兩種析構(gòu)方式,其中堆上對(duì)象的析構(gòu)和棧上對(duì)象的析構(gòu)不同之處在于,棧內(nèi)存的析構(gòu)不需要執(zhí)行 delete 函數(shù),會(huì)自動(dòng)被回收。

為什么構(gòu)造函數(shù)不能是虛函數(shù)?

構(gòu)造函數(shù)就是為了在編譯階段確定對(duì)象的類型以及為對(duì)象分配空間,如果類中有虛函數(shù),那就會(huì)在構(gòu)造函數(shù)中初始化虛函數(shù)表,虛函數(shù)的執(zhí)行卻需要依賴虛函數(shù)表。如果構(gòu)造函數(shù)是虛函數(shù),那它就需要依賴虛函數(shù)表才可執(zhí)行,而只有在構(gòu)造函數(shù)中才會(huì)初始化虛函數(shù)表,雞生蛋蛋生雞的問(wèn)題,很矛盾,所以構(gòu)造函數(shù)不能是虛函數(shù)。

為什么基類析構(gòu)函數(shù)要是虛函數(shù)?

一般基類的析構(gòu)函數(shù)都要設(shè)置成虛函數(shù),因?yàn)槿绻辉O(shè)置成虛函數(shù),在析構(gòu)的過(guò)程中只會(huì)調(diào)用到基類的析構(gòu)函數(shù)而不會(huì)調(diào)用到子類的析構(gòu)函數(shù),可能會(huì)產(chǎn)生內(nèi)存泄漏。

小總結(jié)

offset_to_top對(duì)象在對(duì)象布局中與對(duì)象頂部地址的偏移量。

RTTI指針指向存儲(chǔ)運(yùn)行時(shí)類型信息(type_info)的地址,用于運(yùn)行時(shí)類型識(shí)別,用于typeid和dynamic_cast。

vbase_offset對(duì)象在對(duì)象布局中與指向虛基類虛函數(shù)表的指針地址的偏移量。

vcall_offset父類引用或指針指向子類對(duì)象,調(diào)用被子類重寫的方法時(shí),用于對(duì)虛函數(shù)執(zhí)行指針地址調(diào)整,方便成功調(diào)用被重寫的方法。

thunk: 表示上面虛函數(shù)表中帶有adjustment字段的函數(shù)調(diào)用需要先進(jìn)行this指針調(diào)整,才可以調(diào)用到被子類重寫的函數(shù)。

最后通過(guò)兩張圖總結(jié)一下對(duì)象在Linux中的布局:

A *a = new Derive(); // A為Derive的基類

如圖:

a作為對(duì)象指針存儲(chǔ)在棧中,指向在堆中的類A的實(shí)例內(nèi)存,其中實(shí)例內(nèi)存布局中有虛函數(shù)表指針,指針指向的虛函數(shù)表存放在數(shù)據(jù)段中,虛函數(shù)表中的各個(gè)函數(shù)指針指向的函數(shù)在代碼段中。

虛表結(jié)構(gòu)大體如上圖,正常的虛表結(jié)構(gòu)中都含有后三項(xiàng),當(dāng)有虛繼承情況下會(huì)有前兩個(gè)表項(xiàng)。

參考資料:

https://www.cnblogs.com/qg-whz/p/4909359.html
https://blog.csdn.net/fuzhongmin05/article/details/59112081
https://zhuanlan.zhihu.com/p/67177829
https://mp.weixin.qq.com/s/sqpwQpPYBFkPWCmccruvNw
https://jacktang816.github.io/post/virtualfunction/
https://blog.mengy.org/cpp-virtual-table-2/
https://blog.mengy.org/cpp-virtual-table-1/
https://blog.mengy.org/extend-gdb-with-python/
https://www.zhihu.com/question/389546003/answer/1194780618
https://www.zhihu.com/question/29251261/answer/1297439131
https://zhuanlan.zhihu.com/p/41309205
https://wizardforcel.gitbooks.io/100-gdb-tips/examine-memory.html
https://www.cnblogs.com/xhb19960928/p/11720314.html
https://www.lagou.com/lgeduarticle/113008.html





c++11新特性,所有知識(shí)點(diǎn)都在這了!

你的c++團(tuán)隊(duì)還在禁用異常處理嗎?

JNI編程如何巧妙獲取JNIEnv

Linux 為什么要?jiǎng)討B(tài)鏈接?與靜態(tài)鏈接的區(qū)別是什么?

內(nèi)存對(duì)齊之格式修訂版

c++11新特性之智能指針

gcc a.c 究竟經(jīng)歷了什么?

談?wù)劤绦蜴溄蛹胺侄文切┦?/span>


「 在看的,麻煩點(diǎn)一下再走~ 」

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動(dòng)電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動(dòng)電源

在工業(yè)自動(dòng)化蓬勃發(fā)展的當(dāng)下,工業(yè)電機(jī)作為核心動(dòng)力設(shè)備,其驅(qū)動(dòng)電源的性能直接關(guān)系到整個(gè)系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動(dòng)勢(shì)抑制與過(guò)流保護(hù)是驅(qū)動(dòng)電源設(shè)計(jì)中至關(guān)重要的兩個(gè)環(huán)節(jié),集成化方案的設(shè)計(jì)成為提升電機(jī)驅(qū)動(dòng)性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機(jī) 驅(qū)動(dòng)電源

LED 驅(qū)動(dòng)電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個(gè)照明設(shè)備的使用壽命。然而,在實(shí)際應(yīng)用中,LED 驅(qū)動(dòng)電源易損壞的問(wèn)題卻十分常見,不僅增加了維護(hù)成本,還影響了用戶體驗(yàn)。要解決這一問(wèn)題,需從設(shè)計(jì)、生...

關(guān)鍵字: 驅(qū)動(dòng)電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動(dòng)電源的公式,電感內(nèi)電流波動(dòng)大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計(jì) 驅(qū)動(dòng)電源

電動(dòng)汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動(dòng)汽車的核心技術(shù)之一是電機(jī)驅(qū)動(dòng)控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機(jī)驅(qū)動(dòng)系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動(dòng)汽車的動(dòng)力性能和...

關(guān)鍵字: 電動(dòng)汽車 新能源 驅(qū)動(dòng)電源

在現(xiàn)代城市建設(shè)中,街道及停車場(chǎng)照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進(jìn)步,高亮度白光發(fā)光二極管(LED)因其獨(dú)特的優(yōu)勢(shì)逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動(dòng)電源 LED

LED通用照明設(shè)計(jì)工程師會(huì)遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動(dòng)電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動(dòng)電源的電磁干擾(EMI)問(wèn)題成為了一個(gè)不可忽視的挑戰(zhàn)。電磁干擾不僅會(huì)影響LED燈具的正常工作,還可能對(duì)周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來(lái)解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動(dòng)電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機(jī)重量也有所下降,所以,現(xiàn)在的LED驅(qū)動(dòng)電源

關(guān)鍵字: LED 驅(qū)動(dòng)電源 開關(guān)電源

LED驅(qū)動(dòng)電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動(dòng)LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動(dòng)電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動(dòng)電源
關(guān)閉