什么是繼承?
什么是多重繼承?
多重繼承存在變量和函數(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)Animal
。Cat
類(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)Dog
和CatDog
,來(lái)構(gòu)成一個(gè)鉆石型的繼承結(jié)構(gòu)椿每。Cat
和Dog
都繼承自Animal
伊者,CatDog
多重繼承Cat
和Dog
(你可以把它當(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ù)speak
在CatDog
中都保存了兩份。我們把此時(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ō)中的虛繼承。
四殷勘、虛繼承
接著上面的例子此再,只需要將Cat
和Dog
繼承自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ì)age
和speak
的調(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ù)摹恰。如果Cat
和Dog
中沒(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