C++虛函數(shù)和虛繼承探秘

什么是繼承?
什么是多重繼承?
多重繼承存在變量和函數(shù)名沖突怎么辦陡蝇?
子類(lèi)對(duì)象和父類(lèi)對(duì)象的內(nèi)存模型是什么樣的痊臭?
虛繼承如何解決多重繼承沖突問(wèn)題?

本文將深入C++的底層實(shí)現(xiàn)登夫,從內(nèi)存結(jié)構(gòu)广匙、匯編語(yǔ)言的層面分析這些問(wèn)題的答案。

一恼策、單繼承

繼承是面向?qū)ο缶幊痰暮诵难恢拢梢允箖蓚€(gè)類(lèi)具有所謂的“父子”關(guān)系,子類(lèi)繼承父類(lèi)所有的可見(jiàn)成員涣楷。我們先考慮單繼承的情況分唾,即子類(lèi)只有一個(gè)“父親”。

// File: VirtualInherit.cpp

class Animal {
public:
    int age;
    virtual void speak() {}
};

class Cat : public Animal {
public:
    virtual void speak() {}
};

int main()
{
        Animal* animal = new Cat();
        animal->speak();
}

這里狮斗,父類(lèi)是Animal绽乔,子類(lèi)Cat繼承自Animal,這在邏輯上是合理的碳褒。父類(lèi)有一個(gè)虛函數(shù)speak折砸,但實(shí)現(xiàn)為空。子類(lèi)重寫(xiě)該函數(shù)的實(shí)現(xiàn)沙峻,使得對(duì)speak的調(diào)用呈現(xiàn)出多態(tài)的特性(多態(tài)是指Animal* animal = new Cat(); animal->speak();這段代碼實(shí)際調(diào)用的是Cat類(lèi)的speak方法睦授,父類(lèi)指針調(diào)用虛函數(shù)的時(shí)候會(huì)自動(dòng)找到對(duì)象的實(shí)際類(lèi)型對(duì)應(yīng)的實(shí)現(xiàn))。

看到這里摔寨,我猜大家已經(jīng)覺(jué)得無(wú)聊了去枷,下面我們深入單繼承的內(nèi)存模型是复,看看所謂的虛函數(shù)表和虛函數(shù)表指針都在哪里删顶。

我們使用Microsoft Visual C++中的cl編譯器,它提供了一個(gè)-d1reportAllClassLayout參數(shù)淑廊,可以在編譯時(shí)打印出所有類(lèi)的內(nèi)存模型逗余。在命令行中執(zhí)行如下命令

cl -d1reportAllClassLayout VirtualInherit.cpp

打印出的結(jié)果就是Animal類(lèi)和Cat類(lèi)的內(nèi)存結(jié)構(gòu)和虛函數(shù)表的結(jié)構(gòu)(其它無(wú)關(guān)的類(lèi)結(jié)構(gòu)就不貼出來(lái)了)。

class Animal    size(8):
        +---
 0      | {vfptr}
 4      | age
        +---

Animal::$vftable@:
        | &Animal_meta
        |  0
 0      | &Animal::speak

Animal::speak this adjustor: 0


class Cat       size(8):
        +---
        | +--- (base class Animal)
 0      | | {vfptr}
 4      | | age
        | +---
        +---

Cat::$vftable@:
        | &Cat_meta
        |  0
 0      | &Cat::speak

Cat::speak this adjustor: 0

可以看到蒋纬,Animal類(lèi)占用8個(gè)字節(jié)猎荠,前4個(gè)字節(jié)是虛函數(shù)表指針(這里用的是32位編譯器,所以指針占4個(gè)字節(jié))蜀备,后4個(gè)字節(jié)是數(shù)據(jù)成員关摇。Animal類(lèi)的虛函數(shù)表中只有一個(gè)函數(shù)指針&Animal::speak

同樣地碾阁,Cat類(lèi)也占用了8個(gè)字節(jié)输虱,不過(guò)這8個(gè)字節(jié)都繼承自父類(lèi)AnimalCat類(lèi)的虛函數(shù)表也繼承自Animal脂凶,但由于重寫(xiě)了speak函數(shù)宪睹,所以虛函數(shù)表中的函數(shù)指針從&Animal::speak變成了&Cat::speak愁茁。

細(xì)心的朋友會(huì)發(fā)現(xiàn)在虛函數(shù)表后面還打印出了一句話(huà)Animal::speak this adjustor: 0,這個(gè)數(shù)表示的是Animal::speak函數(shù)所在的虛函數(shù)表對(duì)應(yīng)的虛函數(shù)表指針相對(duì)于this的偏移量亭病《旌埽可能不太容易理解,因?yàn)檫@個(gè)例子中每個(gè)類(lèi)只有一個(gè)虛函數(shù)表罪帖,對(duì)應(yīng)的虛函數(shù)表指針也都處于對(duì)象內(nèi)存空間的起始位置促煮,因此是0。后面我們介紹到多重繼承的時(shí)候整袁,類(lèi)可能擁有多個(gè)虛函數(shù)表菠齿,adjustor就不一定是0了。

另外坐昙,請(qǐng)讀者不要誤認(rèn)為adjustor是對(duì)象內(nèi)存模型的一部分绳匀。事實(shí)上,對(duì)象中不包含adjustor炸客,虛函數(shù)表中也不包含adjustor疾棵。它只是編譯器編譯過(guò)程的中間產(chǎn)物,體現(xiàn)在匯編代碼就就是一個(gè)立即數(shù)而已嚷量。

在進(jìn)一步介紹多重繼承之前陋桂,我認(rèn)為有必要從匯編的層面上徹底分析一下虛函數(shù)的調(diào)用過(guò)程逆趣。

二蝶溶、從匯編碼分析虛函數(shù)調(diào)用

如果你電腦上裝有Visual Studio,建議你也按照下面的步驟把C++代碼編譯成匯編碼試試宣渗。

執(zhí)行命令

cl VirtualInherit.cpp /FAs /Od

其中抖所,/FAs參數(shù)用于生成.asm匯編碼文件,/Od用于禁止編譯器優(yōu)化痕囱。

由于編譯出來(lái)的匯編碼比較長(zhǎng)田轧,為了方便大家理解,我把它拆成幾部分鞍恢,并舍去不重要的內(nèi)容傻粘。

1.函數(shù)聲明和虛函數(shù)表

在匯編代碼的開(kāi)頭,聲明了許多將在后面定義的函數(shù)帮掉,類(lèi)似于C++中“先聲明后定義”弦悉。之后分別定義了Animal類(lèi)和Cat類(lèi)的虛函數(shù)表。

    TITLE   D:\c++Projects\VirtualInheritDemo\VirtualInheritDemo\VirtualInherit.cpp
    .686P                             ; 686P指令集
    .XMM                              ; XMM指令集
    include listing.inc               ; 包含文件
    .model  flat                      ; Flat存儲(chǔ)模型

INCLUDELIB LIBCMT                     ; 包含庫(kù)文件
INCLUDELIB OLDNAMES                   ; 包含庫(kù)文件

PUBLIC  ?speak@Animal@@UAEXXZ         ; Animal::speak
PUBLIC  ??0Animal@@QAE@XZ             ; Animal::Animal
PUBLIC  ?speak@Cat@@UAEXXZ            ; Cat::speak
PUBLIC  ??0Cat@@QAE@XZ                ; Cat::Cat
PUBLIC  _main
PUBLIC  ??_7Animal@@6B@               ; Animal::`vftable'
PUBLIC  ??_7Cat@@6B@                  ; Cat::`vftable'
EXTRN   ??2@YAPAXI@Z:PROC             ; operator new
EXTRN   ??_7type_info@@6B@:QWORD      ; type_info::`vftable'

;  COMDAT ??_7Cat@@6B@
CONST   SEGMENT
    DD  FLAT:??_R4Cat@@6B@
??_7Cat@@6B@                          ; Cat::`vftable'
    DD  FLAT:?speak@Cat@@UAEXXZ
CONST   ENDS

;  COMDAT ??_7Animal@@6B@
CONST   SEGMENT
    DD  FLAT:??_R4Animal@@6B@
??_7Animal@@6B@                       ; Animal::`vftable'
    DD  FLAT:?speak@Animal@@UAEXXZ
CONST   ENDS

這里沒(méi)有太多值得關(guān)注的蟆炊。唯一讓我們不太習(xí)慣的是稽莉,編譯器把函數(shù)名后面加了很多奇怪的字符,這是編譯器為了方便區(qū)分各種函數(shù)的類(lèi)型和參數(shù)涩搓,對(duì)函數(shù)名做了調(diào)整污秆,習(xí)慣就好劈猪。

此外,兩個(gè)虛函數(shù)表都由CONST SEGMENT包圍良拼,表示它們屬于常量數(shù)據(jù)段战得。

2.Cat::Cat構(gòu)造函數(shù)

在查看Cat類(lèi)的構(gòu)造函數(shù)之前,我們要了解一些寄存器使用慣例庸推,才能更輕松的理解程序厚骗。

  • ebp為幀指針,指向棧底(棧底是高地址端棘街,因?yàn)闂J窍虻偷刂贩较驍U(kuò)展的)槐秧。
  • esp為棧指針,指向棧頂(低地址端)掖蛤。
  • 在棧動(dòng)態(tài)變化的過(guò)程中杀捻,通常ebp不變,esp變化蚓庭,push操作使esp自動(dòng)減4致讥,pop操作使esp自動(dòng)加4。
  • ecx用來(lái)傳遞this指針器赞。
  • 函數(shù)返回結(jié)果保存在eax寄存器中垢袱。

如果你無(wú)法理解上面的敘述,推薦看我的另一篇文章《函數(shù)調(diào)用椄酃瘢》请契。

下面我們來(lái)看Cat::Cat構(gòu)造函數(shù)的代碼,這里涉及到虛函數(shù)表指針的設(shè)置夏醉,因此值得關(guān)注爽锥。

; Function compile flags: /Odtp
;   COMDAT ??0Cat@@QAE@XZ
_TEXT   SEGMENT
_this$ = -4                           ; size = 4
??0Cat@@QAE@XZ PROC                   ; Cat::Cat, COMDAT
; _this$ = ecx
    push    ebp
    mov ebp, esp
    push    ecx                       ; 保護(hù)現(xiàn)場(chǎng)
    mov DWORD PTR _this$[ebp], ecx    ; 把ecx寄存器的值復(fù)制到地址ebp+_this$處,即保存this指針的副本
    mov ecx, DWORD PTR _this$[ebp]    ; 把this指針賦值給ecx寄存器
    call    ??0Animal@@QAE@XZ         ; 調(diào)用基類(lèi)Animal的構(gòu)造函數(shù)
    mov eax, DWORD PTR _this$[ebp]    ; 把this指針賦值給eax寄存器
    mov DWORD PTR [eax], OFFSET ??_7Cat@@6B@  ; 把虛函數(shù)表的地址復(fù)制到地址eax處畔柔,即Cat對(duì)象的首地址處
    mov eax, DWORD PTR _this$[ebp]    ; 把this指針賦值給eax寄存器
    mov esp, ebp
    pop ebp
    ret 0
??0Cat@@QAE@XZ ENDP                 ; Cat::Cat
_TEXT   ENDS

這里的關(guān)鍵操作是把虛函數(shù)表的地址氯夷,即虛函數(shù)表指針?lè)诺搅?code>Cat對(duì)象的首地址處,也就是this指針指向的位置靶擦。后面我們將看到編譯器這樣做的良苦用心腮考。

3.主函數(shù)

主函數(shù)部分的匯編代碼如下。

; Function compile flags: /Odtp
; File d:\c++projects\virtualinheritdemo\virtualinheritdemo\virtualinherit.cpp
_TEXT   SEGMENT
_animal$ = -12                        ; size = 4玄捕,在棧中申請(qǐng)的空間大小踩蔚,下同
tv75 = -8                             ; size = 4
$T1 = -4                              ; size = 4
_main   PROC

; 23   : {

    push    ebp
    mov ebp, esp
    sub esp, 12                       ; 0000000cH

; 24   :    Animal* animal = new Cat;

    push    8                         ; 為operator new函數(shù)準(zhǔn)備參數(shù)8
    call    ??2@YAPAXI@Z              ; 調(diào)用operator new函數(shù),參數(shù)為申請(qǐng)的空間大小
    add esp, 4                        ; 椬ぃ縮小4字節(jié)
    mov DWORD PTR $T1[ebp], eax       ; 將operator new函數(shù)的返回值復(fù)制到地址ebp+$T1處
                                      ; operator new的返回值是申請(qǐng)的內(nèi)存空間的首地址
    cmp DWORD PTR $T1[ebp], 0         ; 判斷值是否為0
    je  SHORT $LN3@main               ; 為0則跳轉(zhuǎn)到$LN3處寂纪,說(shuō)明申請(qǐng)空間失敗
    mov ecx, DWORD PTR $T1[ebp]       ; 否則把該值賦值給ecx寄存器,按照慣例,ecx用于傳遞this指針
    call    ??0Cat@@QAE@XZ            ; 調(diào)用Cat::Cat構(gòu)造函數(shù)捞蛋,參數(shù)為剛才申請(qǐng)的空間的首地址
                                      ; 也就是this指針
    mov DWORD PTR tv75[ebp], eax      ; 將構(gòu)造函數(shù)返回值復(fù)制到地址ebp+tv75處
                                      ; 注意孝冒,C++語(yǔ)義中構(gòu)造函數(shù)沒(méi)有返回值,但在匯編層面上有返回值拟杉,返回值是this指針
    jmp SHORT $LN4@main               ; 跳轉(zhuǎn)到$LN4處
$LN3@main:
    mov DWORD PTR tv75[ebp], 0        ; 地址ebp+tv75處賦值0
$LN4@main:
    mov eax, DWORD PTR tv75[ebp]      ; 把地址ebp+tv75處的值賦值給eax寄存器
    mov DWORD PTR _animal$[ebp], eax  ; 把eax寄存器的值復(fù)制到地址ebp+_animal$處(仍然是this指針)

; 25   :    animal->speak();

    mov ecx, DWORD PTR _animal$[ebp]  ; 把this指針賦值給ecx寄存器
    mov edx, DWORD PTR [ecx]          ; 把this指針指向的值賦值給edx寄存器庄涡,即虛函數(shù)表指針
    mov ecx, DWORD PTR _animal$[ebp]  ; 把this指針賦值給ecx寄存器
    mov eax, DWORD PTR [edx]          ; 把虛函數(shù)表指針指向的值賦值給eax寄存器,即虛函數(shù)的地址
                                      ; 這里由于只有一個(gè)虛函數(shù)搬设,因此是[edx]穴店,若調(diào)用第二個(gè)虛函數(shù),則為[edx+4]
    call    eax                       ; 調(diào)用虛函數(shù)

; 26   : }

    xor eax, eax
    mov esp, ebp
    pop ebp
    ret 0
_main   ENDP
_TEXT   ENDS

這段代碼比較長(zhǎng)拿穴,但其實(shí)我們只需要關(guān)注最后調(diào)用虛函數(shù)Cat::speak的部分泣洞。可以發(fā)現(xiàn)默色,并沒(méi)有直接通過(guò)Cat::speak的常量地址?speak@Cat@@UAEXXZ來(lái)調(diào)用球凰,而是從this指針找到虛函數(shù)表,再找到虛函數(shù)表的第一項(xiàng)腿宰,把該項(xiàng)的值作為函數(shù)指針來(lái)調(diào)用呕诉。這就是虛函數(shù)調(diào)用的秘密!

三吃度、多重繼承中的變量沖突

現(xiàn)在甩挫,我們?cè)黾觾蓚€(gè)類(lèi)DogCatDog,來(lái)構(gòu)成一個(gè)鉆石型的繼承結(jié)構(gòu)椿每。CatDog都繼承自Animal伊者,CatDog多重繼承CatDog(你可以把它當(dāng)做貓和狗的雜交...)。

class Animal {
public:
    int age;
    virtual void speak() {}
};

class Cat : public Animal {
public:
    virtual void speak() {}
    virtual void scratch() {}
};

class Dog : public Animal {
public:
    virtual void speak() {}
    virtual void bite() {}
};

class CatDog : public Cat, Dog {
};

在這種繼承結(jié)構(gòu)中拖刃,下面的調(diào)用會(huì)出錯(cuò)删壮。

CatDog catDog;
int age = catDog.age;          // 錯(cuò)誤:"CatDog::age"不明確
catDog.speak();                // 錯(cuò)誤:"CatDog::speak"不明確

因?yàn)?code>CatDog類(lèi)會(huì)把所有父類(lèi)的成員各保存一份贪绘,兩個(gè)父類(lèi)又包含了相同的Animal類(lèi)的成員兑牡,此時(shí)Animal類(lèi)的成員變量age和成員函數(shù)speakCatDog中都保存了兩份。我們把此時(shí)的內(nèi)存模型打印出來(lái)看一下税灌。

class Animal    size(8):
        +---
 0      | {vfptr}
 4      | age
        +---

Animal::$vftable@:
        | &Animal_meta
        |  0
 0      | &Animal::speak

Animal::speak this adjustor: 0


class Cat       size(8):
        +---
        | +--- (base class Animal)
 0      | | {vfptr}
 4      | | age
        | +---
        +---

Cat::$vftable@:
        | &Cat_meta
        |  0
 0      | &Cat::speak
 1      | &Cat::scratch

Cat::speak this adjustor: 0
Cat::scratch this adjustor: 0


class Dog       size(8):
        +---
        | +--- (base class Animal)
 0      | | {vfptr}
 4      | | age
        | +---
        +---

Dog::$vftable@:
        | &Dog_meta
        |  0
 0      | &Dog::speak
 1      | &Dog::bite

Dog::speak this adjustor: 0
Dog::bite this adjustor: 0


class CatDog    size(16):
        +---
        | +--- (base class Cat)
        | | +--- (base class Animal)
 0      | | | {vfptr}
 4      | | | age
        | | +---
        | +---
        | +--- (base class Dog)
        | | +--- (base class Animal)
 8      | | | {vfptr}
12      | | | age
        | | +---
        | +---
        +---

CatDog::$vftable@Cat@:
        | &CatDog_meta
        |  0
 0      | &Cat::speak
 1      | &Cat::scratch

CatDog::$vftable@Dog@:
        | -8
 0      | &Dog::speak
 1      | &Dog::bite

重點(diǎn)查看CatDog類(lèi)的內(nèi)存結(jié)構(gòu)均函,可以發(fā)現(xiàn),首先存放的是base class Cat的內(nèi)容菱涤,里面嵌套了Animal的虛函數(shù)表和成員變量苞也,之后是base class Dog的內(nèi)容,里面也嵌套了同樣的Animal的虛函數(shù)表和成員變量粘秆。所以我們直接調(diào)用age或者speak的時(shí)候如迟,編譯器無(wú)法確定我們到底想調(diào)用哪個(gè)成員變量或是哪個(gè)虛函數(shù)表中的speak

解決方案當(dāng)然是傳說(shuō)中的虛繼承。

四殷勘、虛繼承

接著上面的例子此再,只需要將CatDog繼承自Animal的方式改為虛繼承即可,其它部分不變玲销。寫(xiě)法如下输拇。

class Cat : virtual public Animal {
public:
    virtual void speak() {}
    virtual void scratch() {}
};

class Dog : virtual public Animal {
public:
    virtual void speak() {}
    virtual void bite() {}
};

雖然只加了兩個(gè)virtual關(guān)鍵字,但內(nèi)存結(jié)構(gòu)卻發(fā)生了翻天覆地的變化贤斜,讓我們?cè)俨榭匆幌聝?nèi)存模型策吠。

class Animal    size(8):
        +---
 0      | {vfptr}
 4      | age
        +---

Animal::$vftable@:
        | &Animal_meta
        |  0
 0      | &Animal::speak

Animal::speak this adjustor: 0


class Cat       size(16):
        +---
 0      | {vfptr}
 4      | {vbptr}
        +---
        +--- (virtual base Animal)
 8      | {vfptr}
12      | age
        +---

Cat::$vftable@Cat@:
        | &Cat_meta
        |  0
 0      | &Cat::scratch

Cat::$vbtable@:
 0      | -4
 1      | 4 (Catd(Cat+4)Animal)

Cat::$vftable@Animal@:
        | -8
 0      | &Cat::speak

Cat::speak this adjustor: 8
Cat::scratch this adjustor: 0

vbi:       class  offset o.vbptr  o.vbte fVtorDisp
          Animal       8       4       4 0


class Dog       size(16):
        +---
 0      | {vfptr}
 4      | {vbptr}
        +---
        +--- (virtual base Animal)
 8      | {vfptr}
12      | age
        +---

Dog::$vftable@Dog@:
        | &Dog_meta
        |  0
 0      | &Dog::bite

Dog::$vbtable@:
 0      | -4
 1      | 4 (Dogd(Dog+4)Animal)

Dog::$vftable@Animal@:
        | -8
 0      | &Dog::speak

Dog::speak this adjustor: 8
Dog::bite this adjustor: 0

vbi:       class  offset o.vbptr  o.vbte fVtorDisp
          Animal       8       4       4 0


class CatDog    size(24):
        +---
        | +--- (base class Cat)
 0      | | {vfptr}
 4      | | {vbptr}
        | +---
        | +--- (base class Dog)
 8      | | {vfptr}
12      | | {vbptr}
        | +---
        +---
        +--- (virtual base Animal)
16      | {vfptr}
20      | age
        +---

CatDog::$vftable@Cat@:
        | &CatDog_meta
        |  0
 0      | &Cat::scratch

CatDog::$vftable@Dog@:
        | -8
 0      | &Dog::bite

CatDog::$vbtable@Cat@:
 0      | -4
 1      | 12 (CatDogd(Cat+4)Animal)

CatDog::$vbtable@Dog@:
 0      | -4
 1      | 4 (CatDogd(Dog+4)Animal)

CatDog::$vftable@Animal@:
        | -16
 0      | &CatDog::speak

CatDog::speak this adjustor: 16

vbi:       class  offset o.vbptr  o.vbte fVtorDisp
          Animal      16       4       4 0

有沒(méi)有發(fā)現(xiàn),虛繼承的類(lèi)及其子類(lèi)在虛函數(shù)表指針后面又多了一個(gè)vbptr瘩绒,叫做虛基類(lèi)表指針猴抹。該指針指向的虛基類(lèi)表中包含了一個(gè)偏移量,正是虛基類(lèi)表指針到虛基類(lèi)成員的偏移量锁荔。

我們以CatDog為例分析洽糟。它的內(nèi)存模型為:

class CatDog    size(24):
        +---
        | +--- (base class Cat)
 0      | | {vfptr}
 4      | | {vbptr}
        | +---
        | +--- (base class Dog)
 8      | | {vfptr}
12      | | {vbptr}
        | +---
        +---
        +--- (virtual base Animal)
16      | {vfptr}
20      | age
        +---

base class Cat中的虛基類(lèi)表指針指向的虛基類(lèi)表為

CatDog::$vbtable@Cat@:
 0      | -4
 1      | 12 (CatDogd(Cat+4)Animal)

里面有兩項(xiàng),第一項(xiàng)是該虛基類(lèi)表指針到虛函數(shù)表指針的偏移量堕战,通常為-4坤溃,因?yàn)?code>vfptr和vbptr一般是緊挨著放置的。第二項(xiàng)是該虛基類(lèi)表指針到虛基類(lèi)成員的偏移量嘱丢,我們對(duì)照上面的完整內(nèi)存模型薪介,可以找到虛基類(lèi)Animal的成員首地址是16,而該虛基類(lèi)表指針的地址是4越驻,相差正好12汁政。對(duì)base class Dog可以做同樣的分析,這里就不贅述了缀旁。

現(xiàn)在记劈,使用了虛繼承之后,Animal類(lèi)在CatDog中只有一份并巍,對(duì)agespeak的調(diào)用不會(huì)再產(chǎn)生歧義目木。

不過(guò),事情可能會(huì)比想象中復(fù)雜一些懊渡,如果你按照我的寫(xiě)法親自嘗試一下刽射,可能會(huì)發(fā)現(xiàn)編譯器提示CatDog類(lèi)無(wú)法編譯,提示錯(cuò)誤:虛擬函數(shù) 函數(shù)"Animal::speak"的重寫(xiě)不明確剃执。這是因?yàn)?code>Cat類(lèi)和Dog類(lèi)都重寫(xiě)了speak函數(shù)誓禁,然而CatDog類(lèi)中只能有一份speak函數(shù),編譯器不知道該保留哪個(gè)肾档,所以要求CatDog類(lèi)也重寫(xiě)speak函數(shù)摹恰。如果CatDog中沒(méi)有重寫(xiě)speak或只有一個(gè)重寫(xiě)了speak辫继,就不會(huì)提示錯(cuò)誤。其實(shí)俗慈,只要我們站在編譯器設(shè)計(jì)者的角度思考骇两,就能理清這些紛繁復(fù)雜的規(guī)則。

另外姜盈,虛繼承使用的時(shí)候還需要考慮虛基類(lèi)的初始化低千。本文就不展開(kāi)講了,可以參考百度百科中的解釋馏颂。

本文對(duì)虛繼承的討論只算是淺嘗輒止示血,我們并沒(méi)有深入到匯編層面查看指令到底是如何運(yùn)行的。如果你感興趣救拉,完全可以像本文第二部分一樣难审,把代碼編譯成匯編碼,一行一行查看在虛繼承體系中虛函數(shù)的調(diào)用過(guò)程亿絮,相信一定會(huì)很有收獲告喊。

本文在分析匯編碼時(shí)遇到了一些困難,在此感謝StackOverFlow中熱心網(wǎng)友的解答派昧。如果你自行嘗試的過(guò)程中遇到了問(wèn)題黔姜,請(qǐng)查看文末參考資料中的鏈接,或在評(píng)論區(qū)提問(wèn)蒂萎,我會(huì)盡快答復(fù)秆吵。

參考資料

虛函數(shù)與虛繼承尋蹤 范志東
用匯編分析C++程序 牧秦丶
虛繼承 百度百科
Reversing Microsoft Visual C++ Part II: Classes, Methods and RTTI igorsk
函數(shù)調(diào)用棧 金戈大王
匯編偽指令 strikeshine
匯編寫(xiě)函數(shù):關(guān)于PUBLIC和EXTRN的區(qū)別 襄坤在線(xiàn)
Is vftable[0] stores the first virtual function or RTTI Complete Object Locator? StackOverFlow

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市五慈,隨后出現(xiàn)的幾起案子纳寂,更是在濱河造成了極大的恐慌,老刑警劉巖泻拦,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毙芜,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡争拐,警方通過(guò)查閱死者的電腦和手機(jī)腋粥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)陆错,“玉大人灯抛,你說(shuō)我怎么就攤上這事∫舸桑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵夹抗,是天一觀(guān)的道長(zhǎng)绳慎。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么杏愤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任靡砌,我火速辦了婚禮,結(jié)果婚禮上珊楼,老公的妹妹穿的比我還像新娘通殃。我一直安慰自己,他們只是感情好厕宗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布画舌。 她就那樣靜靜地躺著,像睡著了一般已慢。 火紅的嫁衣襯著肌膚如雪曲聂。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,730評(píng)論 1 289
  • 那天佑惠,我揣著相機(jī)與錄音朋腋,去河邊找鬼。 笑死膜楷,一個(gè)胖子當(dāng)著我的面吹牛旭咽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赌厅,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼轻专,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了察蹲?” 一聲冷哼從身側(cè)響起请垛,我...
    開(kāi)封第一講書(shū)人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎洽议,沒(méi)想到半個(gè)月后宗收,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亚兄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年混稽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片审胚。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖膳叨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情菲嘴,我是刑警寧澤汰翠,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布昭雌,位于F島的核電站,受9級(jí)特大地震影響烛卧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜总放,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望间聊。 院中可真熱鬧,春花似錦哎榴、人聲如沸型豁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)衣形。三九已至,卻和暖如春谆吴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苛预。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留热某,地道東北人腻菇。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像昔馋,于是被迫代替她去往敵國(guó)和親筹吐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • C++虛函數(shù) C++虛函數(shù)是多態(tài)性實(shí)現(xiàn)的重要方式秘遏,當(dāng)某個(gè)虛函數(shù)通過(guò)指針或者引用調(diào)用時(shí)丘薛,編譯器產(chǎn)生的代碼直到運(yùn)行時(shí)才...
    小白將閱讀 1,732評(píng)論 4 19
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,686評(píng)論 0 9
  • 1. 結(jié)構(gòu)體和共同體的區(qū)別垄提。 定義: 結(jié)構(gòu)體struct:把不同類(lèi)型的數(shù)據(jù)組合成一個(gè)整體榔袋,自定義類(lèi)型周拐。共同體uni...
    breakfy閱讀 2,118評(píng)論 0 22
  • 一個(gè)博客铡俐,這個(gè)博客記錄了他讀這本書(shū)的筆記凰兑,總結(jié)得不錯(cuò)∩笄穑《深度探索C++對(duì)象模型》筆記匯總 1. C++對(duì)象模型與內(nèi)...
    Mr希靈閱讀 5,571評(píng)論 0 13
  • 南禪無(wú)古寺吏够, 西天彩霞寧。 香火日日升滩报, 眾信求善行锅知。 我佛若有...
    曹無(wú)傷閱讀 207評(píng)論 0 4