如果乙個類中存在虛函式的話,那麼編譯器就會為這個類生成乙個虛函式表,這個虛函式表中按照個虛函式的宣告順序存放了各個虛函式的位址,需要注意的是,這個虛函式表並不存在於類中,而對於這個類的每個物件,編譯器都會為其生成乙個透明不可見的指標,這個指標就是虛函式表指標,位於該物件記憶體中的開頭,並指向了虛函式表的位置。換句話說,如果乙個類中存在虛函式,假設宣告乙個物件a,在32位的編譯情況下,物件a的記憶體布局中前4個位元組中就存放了虛函式表的位址。
因此,通過這前4個位元組就可以找到虛函式表,而在虛函式表中,按照虛函式的宣告順序存放了各個虛函式位址。
下面,將通過乙個例子來闡述整個過程。
首先定義乙個類a,其中含有兩個整型變數以及3個虛函式。
class a //定義乙個類a,類中有3個虛函式
; virtual void f1();
virtual void f2();
};
然後在主函式中宣告乙個物件a,&a得到物件a的位址。前面說過,物件的記憶體布局中前4個位元組資料為虛函式表的位址,因此將(&a)強制型別轉換為(int *),來把從&a開始的4個位元組當作乙個整體,然後對其進行解引用,就相當於取出這4個位元組中的資料,取出的資料就是虛函式表的位址了。因此虛函式表的位址就是*(int *)(&a)。
int _tmain(int argc, _tchar* ar**)
得到了虛函式表的位址,就能夠找到虛函式表了。前面說過,虛函式表中存放的是各個虛函式的位址,在32位編譯環境下,每個虛函式位址由4個位元組構成,也就是說,從虛函式表的首位址開始,前4個位元組構成了第乙個宣告的虛函式f()位址,接著4個位元組構成了f1()的位址……
因此,通過虛函式表的位址就可以很容易得到每個虛函式的位址了:假設虛函式表的位址為addr,那麼與前面類似,先將其轉換為(int *)型,然後解引用,就可以得到開頭的4個位元組了,即*(int *)
*(int *)(&a),接著要想獲得f1()的位址,既可以直接將虛函式表的位址偏移4個位元組到f1,也可以將4個位元組作為乙個整體偏移1到f1,如下所示。
int addr=*(int *)*(int *)(&a); //虛表位址
int faddr = *(int *)*(int *)(&a);//f()位址
//int addr1 = *(int *)(*(int *)(&a)+4); //f1()位址,偏移4個位元組直接到f1()
int f1addr = *((int *)(*(int *)(&a))+ 1); // 或者以4個位元組作為整體 偏移1到f1()
int f2addr = *((int *)(*(int *)(&a)) + 2); //f2()位址
這樣,就可以得到各個虛函式的位址了。為了驗證整個過程的正確性,接下來就通過這些位址來呼叫相應函式來看執行結果。通過函式位址來呼叫函式可以以((void(*)(void))faddr
)
((void(*)(void))faddr)(); //通過f()的位址呼叫f()
((void(*)(void))f1addr)(); //通過f1()的位址呼叫f1()
((void(*)(void))f2addr)(); //通過f2()的位址呼叫f2()
執行結果如下:
根據結果可知,整個虛函式位址的獲取方式是正確的。
虛函式表指標以及虛函式表建立時機
一 虛函式表指標 vptr 建立時機 vptr跟著物件走,所以物件什麼時候建立出來,vptr就什麼時候建立出來,也就是執行的時候。當程式在編譯期間,編譯器會為建構函式中增加為vptr賦值的 這是編譯器的行為 當程式在執行時,遇到建立物件的 執行物件的建構函式,那麼這個建構函式裡有為這個物件的vptr...
C 虛函式表
考慮最簡單的有虛函式的繼承關係 class f class s public f 此時,我們可以定義乙個父類的指標,實際指向乙個子類的物件。呼叫func函式的結果是子類的函式。虛函式在這裡是動態繫結的。f f new s f func 輸出s func 我們知道子類即使不定義虛函式也會繼承該虛函式表...
C 虛函式表
一般來說,對於開發者我們只需要知道虛函式的使用方法,以及虛函式表的存在即可。但面試時往往會遇到更細節的問題,比如讓你實現乙個虛函式機制,雖然不太實用,總歸了解些底層知識也是件好事。但如果有人苦苦相逼一定要拿這個刷人,你就去罵他吧,你才是寫編譯器的,你們全家都是寫編譯器的。唉,我有些失態了.1.虛函式...