C++ 虛函數(shù)表圖文解析
一、前言
一直以來(lái),對(duì)虛函數(shù)的理解僅僅是,在父類中定義虛函數(shù),子類中可以重寫該虛函數(shù),并且父類指針可以指向子類對(duì)象,調(diào)用子類的虛函數(shù)(多態(tài))。在讀研階段經(jīng)歷的幾個(gè)項(xiàng)目中,自己所寫的類中并沒(méi)有用到虛函數(shù),對(duì)虛函數(shù)這個(gè)東西的強(qiáng)大之處并沒(méi)有太多體會(huì)。最近,學(xué)了設(shè)計(jì)模式中的簡(jiǎn)單工廠模式,對(duì)多態(tài)有了具體的認(rèn)識(shí)。于是,補(bǔ)了補(bǔ)多態(tài)、虛函數(shù)、虛函數(shù)表相關(guān)的知識(shí),參考相關(guān)博客,加上自己的理解,整理了這篇博文。
二、含有虛函數(shù)類的內(nèi)存模型
以下面的類為例(32位平臺(tái)下):
class Father { public: virtual void fun1() { cout << "Father::fun1()" << endl; } virtual void fun2() { cout << "Father::fun2()" << endl; } int i; };
該類中含有兩個(gè)虛函數(shù)和一個(gè)成員變量i,輸出sizeof(Father),結(jié)果為8個(gè)字節(jié)。如果去掉virtual關(guān)鍵字,則結(jié)果為4個(gè)字節(jié)。也就是說(shuō),類中含有虛函數(shù),則該類會(huì)增加4個(gè)字節(jié),那這4個(gè)字節(jié)是什么變量所占據(jù)的呢?
答案是一個(gè)指針(我覺(jué)得應(yīng)該是unsigned int*類型指針,這點(diǎn)不確定),在vs調(diào)試窗口中,可以看到該指針名為_(kāi)vfptr,該指針?lè)Q為虛函數(shù)表指針。
類的內(nèi)存模型如下,_vptr指針和成員變量i各占4字節(jié),一共8字節(jié)。另外 ,-vptr指針一定在內(nèi)存模型前面。對(duì)于只有一個(gè)虛表指針的類來(lái)說(shuō),類內(nèi)存模型前4個(gè)字節(jié)就是虛表指針?biāo)伎臻g。
三、虛函數(shù)表與虛函數(shù)表指針
上面提到_vfptr是虛函數(shù)表指針,那虛函數(shù)表是什么呢?
虛函數(shù)表其實(shí)就是一個(gè)指針數(shù)組
,這個(gè)數(shù)組中存放著虛函數(shù)的地址,大概如下:
最后一個(gè)類似于字符串的結(jié)束標(biāo)志位,VS編譯器中為0。
這樣的話,虛函數(shù)表指針就很容易理解了,這個(gè)虛函數(shù)表指針指向該虛函數(shù)表,也就是虛函數(shù)指針的值就是上述指針數(shù)組的首地址。
四、虛函數(shù)地址
函數(shù)存放在代碼區(qū),虛函數(shù)也不例外。虛函數(shù)表中存放的是虛函數(shù)地址,即代碼區(qū)虛函數(shù)的入口地址。
五、多態(tài)
定義一個(gè)Father的子類Son,對(duì)虛函數(shù)fun1()進(jìn)行重寫。
#include<iostream> using namespace std; class Father { public: virtual void fun1() { cout << "Father::fun1()" << endl; } virtual void fun2() { cout << "Father::fun2()" << endl; } int i; }; class Son :public Father { virtual void fun1() { cout << "Son::fun1()" << endl; } }; int main() { Son son; Father father; Father *p = &father; p->fun1(); p->fun2(); p=&son; p->fun1(); p->fun2(); return 0; }
父類中有虛函數(shù),則子類同樣會(huì)有一個(gè)虛函數(shù)指針,這個(gè)指針指向一個(gè)新表,如下圖所示:
Son類重寫了fun1(),未重寫fun2(),那么虛函數(shù)表中,第一個(gè)地址便是重寫的Son::fun1()的地址,第二個(gè)地址仍然是父類中Father::fun2()的地址。這里可以在vs調(diào)試模式下,查看father與son的虛函數(shù)表,son表中第二個(gè)元素值與father表中第二個(gè)元素值相同。
Father *p = &father; p->fun1(); p->fun2();
p指向Father對(duì)象father:
p->fun1():沿著框1->框3->框5
的路徑,調(diào)用Father::fun1();
p->fun2():沿著框1->框4->框6
的路徑,調(diào)用Father::fun2();
p=&son; p->fun1(); p->fun2();
p指向子類對(duì)象son:
p->fun1():沿著框7->框9->框11
的路徑,調(diào)用Son::fun1();
p->fun2():沿著框7->框10->框6
的路徑,調(diào)用Father::fun2();
六、通過(guò)函數(shù)指針操作調(diào)用虛函數(shù)
現(xiàn)修改main函數(shù)
typedef void(*Fun)(void); int main() { Father father; Son son; printf("虛函數(shù)表地址_vfptr:%p\n", *(unsigned int*)(&father)); printf("第一個(gè)虛函數(shù)地址e:%p\n", *(unsigned int*)*(unsigned int*)(&father)); printf("第二個(gè)虛函數(shù)地址f:%p\n", *((unsigned int*)*(unsigned int*)(&father)+1)); unsigned char* end = NULL; end = (unsigned char*)((unsigned int*)*(unsigned int*)(&father) + 2); printf("結(jié)束符地址d:%p\n", end); printf("結(jié)束符值:%d\n", *end); Fun pFun = NULL; pFun = (Fun)(*((unsigned int*)*(unsigned int*)(&father) + 1)); pFun(); return 0; }
運(yùn)行結(jié)果:
先看一下vs調(diào)試模式下各變量的值
將之前的圖修改一下,便于理解:
紅框中,father中的_vfptr為0x1270234,對(duì)應(yīng)上圖中的_vfptr,即虛函數(shù)表的地址;
數(shù)組[0]值為0x01261447,對(duì)應(yīng)上圖中的e,即Father::fun1()的地址;
數(shù)組[1]值為0x0126141a,對(duì)應(yīng)上圖中的f,即Father::fun2()的地址。
好了,現(xiàn)在來(lái)看一下程序中這些看起來(lái)很唬人的東西:
printf("虛函數(shù)表地址_vfptr:%p\n", *(unsigned int*)(&father)); printf("第一個(gè)虛函數(shù)地址e:%p\n", *(unsigned int*)*(unsigned int*)(&father)); printf("第二個(gè)虛函數(shù)地址f:%p\n", *((unsigned int*)*(unsigned int*)(&father)+1)); unsigned char* end = NULL; end = (unsigned char*)((unsigned int*)*(unsigned int*)(&father) + 2);
這里,直接用上圖中的符號(hào)進(jìn)行分析,否則,說(shuō)一通xx的地址、對(duì)xx解引用等等,容易把人搞暈。
(1)
虛函數(shù)表的地址*(unsigned int*)(&father)
- a0=&father
- a1=(unsigned int *)a0
- _vfptr=*a1
注意,這里a0和a1的數(shù)值是一樣的,但只有把地址a0強(qiáng)制轉(zhuǎn)換成(unsigned int *)類型,解引用時(shí)系統(tǒng)才會(huì)從該地址向后解析4個(gè)字節(jié)空間,解析成一個(gè)unsinged int類型數(shù)據(jù)。
(2)
第一個(gè)虛函數(shù)地址*(unsigned int*)*(unsigned int*)(&father)
- b=(unsigned int*)_vfptr
- e=*b
(3)
第二個(gè)虛函數(shù)地址*((unsigned int*)*(unsigned int*)(&father)+1)
- c=b+1
- f=*c
(4)
結(jié)束符地址(unsigned char*)((unsigned int*)*(unsigned int*)(&father) + 2)
- 未強(qiáng)制轉(zhuǎn)換的d=b+2
- 強(qiáng)制轉(zhuǎn)換的d=(unsigned char*)(b+2)
(5)
通過(guò)函數(shù)指針調(diào)用虛函數(shù)
Fun pFun = NULL; pFun = (Fun)(*((unsigned int*)*(unsigned int*)(&father) + 1)); pFun();
七、結(jié)語(yǔ)
這篇博文包含許多自己理解的內(nèi)容,并在此基礎(chǔ)上畫了圖解,如果有誤,還請(qǐng)指正。
八、參考
C++ 虛函數(shù)詳解(虛函數(shù)表、vfptr)
到此這篇關(guān)于C++ 虛函數(shù)表圖文解析的文章就介紹到這了,更多相關(guān)C++ 虛函數(shù)表內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言內(nèi)嵌匯編API內(nèi)存搜索引擎實(shí)例
這篇文章主要介紹了C語(yǔ)言內(nèi)嵌匯編API內(nèi)存搜索引擎實(shí)例,涉及匯編語(yǔ)言與內(nèi)存相關(guān)操作,需要的朋友可以參考下2014-10-10C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)系列篇二叉樹(shù)的概念及滿二叉樹(shù)與完全二叉樹(shù)
在上一章中我們正式開(kāi)啟了對(duì)數(shù)據(jù)結(jié)構(gòu)中樹(shù)的講解,介紹了樹(shù)的基礎(chǔ)。本章我們將學(xué)習(xí)二叉樹(shù)的概念,介紹滿二叉樹(shù)和完全二叉樹(shù)的定義,并對(duì)二叉樹(shù)的基本性質(zhì)進(jìn)行一個(gè)簡(jiǎn)單的介紹。本章附帶課后練習(xí)2022-02-02C++發(fā)送HTTP請(qǐng)求的實(shí)現(xiàn)代碼
這篇文章主要介紹了C++發(fā)送HTTP請(qǐng)求的實(shí)現(xiàn)代碼,需要的朋友可以參考下2014-06-06C語(yǔ)言修煉之路悟徹?cái)?shù)組真妙理?巧用下標(biāo)破萬(wàn)敵上篇
在C語(yǔ)言和C++等語(yǔ)言中,數(shù)組元素全為指針變量的數(shù)組稱為指針數(shù)組,指針數(shù)組中的元素都必須具有相同的存儲(chǔ)類型、指向相同數(shù)據(jù)類型的指針變量。指針數(shù)組比較適合用來(lái)指向若干個(gè)字符串,使字符串處理更加方便、靈活2022-02-02c語(yǔ)言中實(shí)現(xiàn)數(shù)組幾個(gè)數(shù)求次大值
這篇文章主要介紹了c語(yǔ)言中實(shí)現(xiàn)數(shù)組幾個(gè)數(shù)求次大值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12C語(yǔ)言科學(xué)計(jì)算入門之矩陣乘法的相關(guān)計(jì)算
這篇文章主要介紹了C語(yǔ)言科學(xué)計(jì)算入門之矩陣乘法的相關(guān)計(jì)算,文章中還介紹了矩陣相關(guān)的斯特拉森算法的實(shí)現(xiàn),需要的朋友可以參考下2015-12-12