c 多型性 vptr和vtable

2021-05-25 12:15:04 字數 2372 閱讀 9139

多型性 (polymorphism) 是物件導向程式設計的基本特徵之一。而在 c++ 中,多型性通過虛函式 (virtual function) 來實現。我們來看一段簡單的**:

#include

using namespace std;

class base

virtual void fun2()

virtual void fun3()

};class a:public base

void fun2()

};void foo (base& obj)

int main()

執行結果為:

base::fun1()

base::fun2()

base::fun3()

a::fun1()

a::fun2()

base::fun3()

僅通過基類的介面,程式呼叫了正確的函式,它就好像知道我們輸入的物件的型別一樣!

那麼,編譯器是如何知道正確**的位置的呢?其實,編譯器在編譯時並不知道要呼叫的函式體的正確位置,但它插入了一段能找到正確的函式體的**。這稱之為 晚** (late binding) 或 執行時** (runtime binding) 技術。

通過virtual 關鍵字建立虛函式能引發晚**,編譯器在幕後完成了實現晚**的必要機制。它對每個包含虛函式的類建立乙個表(稱為vtable),用於放置虛函式的位址。在每個包含虛函式的類中,編譯器秘密地放置了乙個稱之為vpointer(縮寫為vptr)的指標,指向這個物件的vtable。所以無論這個物件包含乙個或是多少虛函式,編譯器都只放置乙個vptr即可。vptr由編譯器在建構函式中秘密地插入的**來完成初始化,指向相應的vtable,這樣物件就「知道」自己是什麼型別了。 vptr都在物件的相同位置,常常是物件的開頭。這樣,編譯器可以容易地找到物件的vtable並獲取函式體的位址。

如果我們用sizeof檢視前面base類的長度,我們就會發現,它的長度不僅僅是乙個int的長度,而是增加了剛好是乙個void指標的長度(在我的機器裡面,乙個int佔4個位元組,乙個void指標佔4個位元組,這樣正好類base的長度為8個位元組)。

每當建立乙個包含虛函式的類或從包含虛函式的類派生乙個類時,編譯器就為這個類建立乙個唯一的vtable。在vtable中,放置了這個類中或是它的基類中所有虛函式的位址,這些虛函式的順序都是一樣的,所以通過偏移量可以容易地找到所需的函式體的位址。假如在派生類中沒有對在基類中的某個虛函式進行重寫(overriding),那末還使用基類的這個虛函式的位址(正如上面的程式結果所示)。

至今為止,一切順利。下面,我們的試驗開始了。

就目前得知的,我們可以試探著通過自己的**來呼叫虛函式,也就是說我們要找尋一下編譯器秘密地插入的那段能找到正確函式體的**的足跡。

如果我們有乙個base指標作為介面,它一定指向乙個base或由base派生的物件,或者是a,或者是其它什麼。這無關緊要,因為vptr的位置都一樣,一般都在物件的開頭。如果是這樣的話,那麼包含有虛函式的物件的指標,例如base指標,指向的位置恰恰是另乙個指標——vptr。vptr指向的 vtable其實就是乙個函式指標的陣列,現在,vptr正指向它的第乙個元素,那是乙個函式指標。如果vptr向後偏移乙個void指標長度的話,那麼它應該指向了vtable中的第二個函式指標了。

這看來就像是乙個指標連成的鏈,我們得從當前指標獲取它指向的下乙個指標,這樣我們才能「順藤摸瓜」。那麼,我來介紹乙個函式:

void *getp (void* p)

我們不考慮它漂亮與否,我們只是試驗。getp() 可以從當前指標獲取它指向的下乙個指標。如果我們能找到函式體的位址,用什麼來儲存它呢?我想應該用乙個函式指標:

typedef void (*fun)();

它與base中的三個虛函式相似,為了簡單我們不要任何輸入和返回,我們只要知道它實際上被執行了即可。

然後,我們負責「摸瓜」的函式登場了:

fun getfun (base* obj, unsigned long off)

第乙個引數是base指標,我們可以輸入base或是base派生物件的指標。第二個引數是vtable偏移量,偏移量如果是0那麼對應fun1(),如果是1對應fun2()。getfun() 返回的是fun型別函式指標,我們上面定義的那個。可以看到,函式首先就對base指標呼叫了一次getp(),這樣得到了vptr這個指標,然後用乙個 unsigned char指標運算偏移量,得到的結果再次輸入getp(),這次得到的就應該是正確的函式體的位置了。

那麼它到底能不能正確工作呢?我們修改main() 來測試一下:

int main()

激動人心的時刻到來了,讓我們執行它!

執行結果為:

a::fun1()

a::fun2()

base::fun3()

至此,我們真的成功了。通過我們的方法,我們獲取了物件的vptr,在它的體外執行了它的虛函式。

c 多型性 vptr和vtable

多型性 polymorphism 是物件導向程式設計的基本特徵之一。而在 c 中,多型性通過虛函式 virtual function 來實現。我們來看一段簡單的 include using namespace std class base 我們不考慮它漂亮與否,我們只是試驗。getp 可以從當前指標...

多型性 vptr和vtable

多型性 polymorphi 是物件導向程式設計的基本特徵之一。而在c 中,多型性通過虛函式 virtual function 來實現。我們來看一段簡單的 include using namespace std class base typedef void fun 它與base中的三個虛函式相似,...

多型性 vptr和vtable

多型性 polymorphism 是物件導向程式設計的基本特徵之一。而在c 中,多型性通過虛函式 virtual function 來實現。我們來看一段簡單的 include using namespace std class base typedef void fun 它與base中的三個虛函式相...