虛擬函式的記憶體佈局(上)
C++中 的虛擬函式的作用主要是實現了多型的機制。關於多型,簡而言之就是用父型別別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式。這種技術 可以讓父類的指標有“多種形態”,這是一種泛型技術。所謂泛型技術,說白了就是試圖使用不變的程式碼來實現可變的演算法。比如:模板技術,RTTI技術,虛擬函式技術,要麼是試圖做到在編譯時決議,要麼試圖做到執行時決議。
關於虛擬函式的使用方法,我在這裡不做過多的闡述。大家可以看看相關的C++的書籍。在這篇文章中,我只想從虛擬函式的實現機制上面為大家 一個清晰的剖析。
當然,相同的文章在網上也出現過一些了,但我總感覺這些文章不是很容易閱讀,大段大段的程式碼,沒有圖片,沒有詳細的說明,沒有比較,沒有舉一反三。不利於學習和閱讀,所以這是我想寫下這篇文章的原因。也希望大家多給我提意見。
言歸正傳,讓我們一起進入虛擬函式的世界。
虛擬函式表
對C++ 瞭解的人都應該知道虛擬函式(Virtual Function)是通過一張虛擬函式表(Virtual Table)來實現的。簡稱為V-Table。 在這個表中,主是要一個類的虛擬函式的地址表,這張表解決了繼承、覆蓋的問題,保證其容真實反應實際的函式。這樣,在有虛擬函式的類的例項中這個表被分配在了 這個例項的記憶體中,所以,當我們用父類的指標來操作一個子類的時候,這張虛擬函式表就顯得由為重要了,它就像一個地圖一樣,指明瞭實際所應該呼叫的函式。
這裡我們著重看一下這張虛擬函式表。在C++的標準規格說明書中說到,編譯器必需要保證虛擬函式表的指標存在於物件例項中最前面的位置(這是為了保證正確取到虛擬函式的偏移量)。 這意味著我們通過物件例項的地址得到這張虛擬函式表,然後就可以遍歷其中函式指標,並呼叫相應的函式。
聽我扯了那麼多,我可以感覺出來你現在可能比以前更加暈頭轉向了。 沒關係,下面就是實際的例子,相信聰明的你一看就明白了。
假設我們有這樣的一個類:
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
按照上面的說法,我們可以通過Base的例項來得到虛擬函式表。 下面是實際例程:
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
cout << "虛擬函式表地址:" << (int*)(&b) << endl;
cout << "虛擬函式表 — 第一個函式地址:" << (int*)*(int*)(&b) << endl;
// Invoke the first virtual function
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
實際執行經果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)
虛擬函式表地址:0012FED4
虛擬函式表 — 第一個函式地址:0044F148
Base::f
通過這個示例,我們可以看到,我們可以通過強行把&b轉成int *,取得虛擬函式表的地址,然後,再次取址就可以得到第一個虛擬函式的地址了,也就是Base::f(),這在上面的程式中得到了驗證(把int* 強制轉成了函式指標)。通過這個示例,我們就可以知道如果要呼叫Base::g()和Base::h(),其程式碼如下:
(Fun)*((int*)*(int*)(&b)+0); // Base::f()
(Fun)*((int*)*(int*)(&b)+1); // Base::g()
(Fun)*((int*)*(int*)(&b)+2); // Base::h()
這個時候你應該懂了吧。什麼?還是有點暈。也是,這樣的程式碼看著太亂了。沒問題,讓我畫個圖解釋一下。如下所示:
注意:在上面這個圖中,我在虛擬函式表的最後多加了一個結點,這是虛擬函式表的結束結點,就像字串的結束符“/0”一樣,其標誌了虛擬函式表的結束。這個結束標誌的值在不同的編譯器下是不同的。在WinXP+VS2003下,這個值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,這個值是如果1,表示還有下一個虛擬函式表,如果值是0,表示是最後一個虛擬函式表。
下面,我將分別說明“無覆蓋”和“有覆蓋”時的虛擬函式表的樣子。沒有覆蓋父類的虛擬函式是毫無意義的。我之所以要講述沒有覆蓋的情況,主要目的是為了給一個對比。在比較之下,我們可以更加清楚地知道其內部的具體實現。
一般繼承(無虛擬函式覆蓋)
下面,再讓我們來看看繼承時的虛擬函式表是什麼樣的。假設有如下所示的一個繼承關係:
請注意,在這個繼承關係中,子類沒有過載任何父類的函式。那麼,在派生類的例項中,其虛擬函式表如下所示:
對於例項:Derive d; 的虛擬函式表如下:
我們可以看到下面幾點:
1)虛擬函式按照其宣告順序放於表中。
2)父類的虛擬函式在子類的虛擬函式前面。
我相信聰明的你一定可以參考前面的那個程式,來編寫一段程式來驗證。
相關文章
- 深入理解Java虛擬機器之JVM記憶體佈局篇Java虛擬機JVM記憶體
- 【記憶體管理】記憶體佈局記憶體
- C++ 虛繼承 物件記憶體佈局C++繼承物件記憶體
- 深入理解Java虛擬機器之物件的記憶體佈局、訪問定位Java虛擬機物件記憶體
- [CPP] 類的記憶體佈局記憶體
- Java物件的記憶體佈局Java物件記憶體
- JVM -- 物件的記憶體佈局JVM物件記憶體
- 聊聊虛擬記憶體記憶體
- Linux 虛擬記憶體Linux記憶體
- 虛擬函式 純虛擬函式函式
- 虛擬函式,虛擬函式表函式
- C程式記憶體佈局C程式記憶體
- OC物件記憶體佈局物件記憶體
- Java物件記憶體佈局Java物件記憶體
- c程式的記憶體佈局圖C程式記憶體
- 【C++筆記】虛擬函式(從虛擬函式表來解析)C++筆記函式
- 【C++筆記】虛擬函式(從虛擬函式概念來解析)C++筆記函式
- 【Java基礎】實體記憶體&虛擬記憶體Java記憶體
- 5.虛擬記憶體記憶體
- 虛擬記憶體有什麼用 虛擬記憶體不足怎麼解決記憶體
- 虛擬記憶體系統——瞭解記憶體的工作原理記憶體
- 介面、虛擬函式、純虛擬函式、抽象類函式抽象
- 虛擬記憶體(待補充)記憶體
- 分散載入與記憶體佈局記憶體
- Rust 程式設計:記憶體佈局Rust程式設計記憶體
- 10-記憶體空間佈局記憶體
- 記憶體管理兩部曲之虛擬記憶體管理記憶體
- 虛擬記憶體對 OI 的影響記憶體
- win10怎麼刪除虛擬記憶體 刪除win10虛擬記憶體的方法Win10記憶體
- win10怎樣刪虛擬記憶體_Win10虛擬記憶體如何關閉Win10記憶體
- Ubuntu 新增虛擬記憶體檔案Ubuntu記憶體
- Linux 虛擬記憶體引數配置Linux記憶體
- 詳細理解Linux虛擬記憶體Linux記憶體
- JAVA 虛擬機器可用記憶體Java虛擬機記憶體
- win10怎麼清除虛擬記憶體 win10系統虛擬記憶體清理的辦法Win10記憶體
- JVM-物件及物件記憶體佈局JVM物件記憶體
- 常用記憶體操作函式記憶體函式
- win10虛擬記憶體怎麼設定 win10虛擬記憶體設定方法Win10記憶體