C++ 虛函數(shù)表圖文解析
一、前言
一直以來,對虛函數(shù)的理解僅僅是,在父類中定義虛函數(shù),子類中可以重寫該虛函數(shù),并且父類指針可以指向子類對象,調用子類的虛函數(shù)(多態(tài))。在讀研階段經(jīng)歷的幾個項目中,自己所寫的類中并沒有用到虛函數(shù),對虛函數(shù)這個東西的強大之處并沒有太多體會。最近,學了設計模式中的簡單工廠模式,對多態(tài)有了具體的認識。于是,補了補多態(tài)、虛函數(shù)、虛函數(shù)表相關的知識,參考相關博客,加上自己的理解,整理了這篇博文。
二、含有虛函數(shù)類的內存模型
以下面的類為例(32位平臺下):
class Father {
public:
virtual void fun1() { cout << "Father::fun1()" << endl; }
virtual void fun2() { cout << "Father::fun2()" << endl; }
int i;
};
該類中含有兩個虛函數(shù)和一個成員變量i,輸出sizeof(Father),結果為8個字節(jié)。如果去掉virtual關鍵字,則結果為4個字節(jié)。也就是說,類中含有虛函數(shù),則該類會增加4個字節(jié),那這4個字節(jié)是什么變量所占據(jù)的呢?
答案是一個指針(我覺得應該是unsigned int*類型指針,這點不確定),在vs調試窗口中,可以看到該指針名為_vfptr,該指針稱為虛函數(shù)表指針。
類的內存模型如下,_vptr指針和成員變量i各占4字節(jié),一共8字節(jié)。另外 ,-vptr指針一定在內存模型前面。對于只有一個虛表指針的類來說,類內存模型前4個字節(jié)就是虛表指針所占空間。

三、虛函數(shù)表與虛函數(shù)表指針
上面提到_vfptr是虛函數(shù)表指針,那虛函數(shù)表是什么呢?
虛函數(shù)表其實就是一個指針數(shù)組,這個數(shù)組中存放著虛函數(shù)的地址,大概如下:

最后一個類似于字符串的結束標志位,VS編譯器中為0。
這樣的話,虛函數(shù)表指針就很容易理解了,這個虛函數(shù)表指針指向該虛函數(shù)表,也就是虛函數(shù)指針的值就是上述指針數(shù)組的首地址。

四、虛函數(shù)地址
函數(shù)存放在代碼區(qū),虛函數(shù)也不例外。虛函數(shù)表中存放的是虛函數(shù)地址,即代碼區(qū)虛函數(shù)的入口地址。

五、多態(tài)
定義一個Father的子類Son,對虛函數(shù)fun1()進行重寫。
#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ù),則子類同樣會有一個虛函數(shù)指針,這個指針指向一個新表,如下圖所示:

Son類重寫了fun1(),未重寫fun2(),那么虛函數(shù)表中,第一個地址便是重寫的Son::fun1()的地址,第二個地址仍然是父類中Father::fun2()的地址。這里可以在vs調試模式下,查看father與son的虛函數(shù)表,son表中第二個元素值與father表中第二個元素值相同。
Father *p = &father; p->fun1(); p->fun2();
p指向Father對象father:
p->fun1():沿著框1->框3->框5的路徑,調用Father::fun1();
p->fun2():沿著框1->框4->框6的路徑,調用Father::fun2();
p=&son; p->fun1(); p->fun2();
p指向子類對象son:
p->fun1():沿著框7->框9->框11的路徑,調用Son::fun1();
p->fun2():沿著框7->框10->框6的路徑,調用Father::fun2();
六、通過函數(shù)指針操作調用虛函數(shù)
現(xiàn)修改main函數(shù)
typedef void(*Fun)(void);
int main()
{
Father father;
Son son;
printf("虛函數(shù)表地址_vfptr:%p\n", *(unsigned int*)(&father));
printf("第一個虛函數(shù)地址e:%p\n", *(unsigned int*)*(unsigned int*)(&father));
printf("第二個虛函數(shù)地址f:%p\n", *((unsigned int*)*(unsigned int*)(&father)+1));
unsigned char* end = NULL;
end = (unsigned char*)((unsigned int*)*(unsigned int*)(&father) + 2);
printf("結束符地址d:%p\n", end);
printf("結束符值:%d\n", *end);
Fun pFun = NULL;
pFun = (Fun)(*((unsigned int*)*(unsigned int*)(&father) + 1));
pFun();
return 0;
}
運行結果:

先看一下vs調試模式下各變量的值

將之前的圖修改一下,便于理解:

紅框中,father中的_vfptr為0x1270234,對應上圖中的_vfptr,即虛函數(shù)表的地址;
數(shù)組[0]值為0x01261447,對應上圖中的e,即Father::fun1()的地址;
數(shù)組[1]值為0x0126141a,對應上圖中的f,即Father::fun2()的地址。
好了,現(xiàn)在來看一下程序中這些看起來很唬人的東西:
printf("虛函數(shù)表地址_vfptr:%p\n", *(unsigned int*)(&father));
printf("第一個虛函數(shù)地址e:%p\n", *(unsigned int*)*(unsigned int*)(&father));
printf("第二個虛函數(shù)地址f:%p\n", *((unsigned int*)*(unsigned int*)(&father)+1));
unsigned char* end = NULL;
end = (unsigned char*)((unsigned int*)*(unsigned int*)(&father) + 2);
這里,直接用上圖中的符號進行分析,否則,說一通xx的地址、對xx解引用等等,容易把人搞暈。
(1)虛函數(shù)表的地址*(unsigned int*)(&father)
- a0=&father
- a1=(unsigned int *)a0
- _vfptr=*a1

注意,這里a0和a1的數(shù)值是一樣的,但只有把地址a0強制轉換成(unsigned int *)類型,解引用時系統(tǒng)才會從該地址向后解析4個字節(jié)空間,解析成一個unsinged int類型數(shù)據(jù)。
(2)第一個虛函數(shù)地址*(unsigned int*)*(unsigned int*)(&father)
- b=(unsigned int*)_vfptr
- e=*b

(3)第二個虛函數(shù)地址*((unsigned int*)*(unsigned int*)(&father)+1)
- c=b+1
- f=*c

(4)結束符地址(unsigned char*)((unsigned int*)*(unsigned int*)(&father) + 2)
- 未強制轉換的d=b+2
- 強制轉換的d=(unsigned char*)(b+2)
(5)通過函數(shù)指針調用虛函數(shù)
Fun pFun = NULL; pFun = (Fun)(*((unsigned int*)*(unsigned int*)(&father) + 1)); pFun();

七、結語
這篇博文包含許多自己理解的內容,并在此基礎上畫了圖解,如果有誤,還請指正。
八、參考
C++ 虛函數(shù)詳解(虛函數(shù)表、vfptr)
到此這篇關于C++ 虛函數(shù)表圖文解析的文章就介紹到這了,更多相關C++ 虛函數(shù)表內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C語言數(shù)據(jù)結構系列篇二叉樹的概念及滿二叉樹與完全二叉樹
在上一章中我們正式開啟了對數(shù)據(jù)結構中樹的講解,介紹了樹的基礎。本章我們將學習二叉樹的概念,介紹滿二叉樹和完全二叉樹的定義,并對二叉樹的基本性質進行一個簡單的介紹。本章附帶課后練習2022-02-02
c語言中實現(xiàn)數(shù)組幾個數(shù)求次大值
這篇文章主要介紹了c語言中實現(xiàn)數(shù)組幾個數(shù)求次大值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12

