C++中的虛函數表是什么時期建立的?


虛函數表是在什么時期建立的?

  最近參加阿里巴巴公司的內推,面試官問了“虛函數表是在什么時期建立的?”。因為以前對虛函數表的理解不夠多,所以就根據程序構建(Build)的四個過程(預編譯、編譯、匯編和鏈接),推導出虛函數表應該是在編譯期確定的,原因如下:

  1)預編譯器主要處理那些源代碼文件中的以“#”開始的預編譯指令,如“#include”、“#define”。很明顯這個過程可以排除。

  2)匯編器是將編譯器生成的匯編代碼轉變成機器可以執行的指令,每一個匯編語句幾乎都對應一條機器指令。匯編過程相對於編譯期來說比較簡單,沒有復雜的語法,也沒有語義,也不需要做指令優化,只是根據匯編指令和機器指令的對照表一一翻譯就行了。所以,匯編期也是可以排除的。

  3)鏈接器(現只考慮靜態鏈接)是將匯編器生成的目標文件(和庫)鏈接成一個可執行文件,本質上做的是重定位(Relocation)的工作,詳細可參考《程序員的自我修養》2.3、2.4節。很明顯鏈接期也是可以排除的。

  4)編譯器要做的事情就比較多了,包括詞法分析、語法分析、語義分析及優化代碼等,是整個程序構建的核心。所以,排除了預編譯期、匯編期、鏈接期及考慮到編譯期所做的事情,虛函數表應該是在編譯期建立的。

  

  上邊給出的答案還是有點不夠全面,因為忽略了動態鏈接。不過,我們在《深度探索C++對象模型》的4.2節能夠找到完美答案,具體摘抄如下:

  “表格中的virtual functions地址是如何被建構起來的?在C++中,virtual functions(可經由其class object被調用)可以在編譯時期獲知。此外,這一組地址是固定不變的,執行期不可能新增或替換之。由於程序執行時,表格的大小和內容都不會改變,所以其建構和存取皆可以由編譯器完全掌控,不需要執行期的任何介入。

虛函數表

   C++中的虛函數的作用主要是實現了多態機制,即父類類別的指針(或者引用)指向其子類的實例,然后通過父類的指針(或者引用)調用實際子類的成員函數。多態機制可以簡單地概括為“一個接口,多種方法”。

  虛函數是通過一張虛函數表(Virtual Table)來實現的,簡稱為V-Table。在這個表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了這個實例的內存中,所以,當我們用父類的指針來操作一個子類的時候,這張虛函數表就顯得極為重要了,它就像一個地圖一樣,指明了實際所應該調用的函數。

  下邊我們通過一個小程序來看看虛函數表到底是怎么樣的?

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Base
 5 {
 6 public:
 7     virtual void fn() { cout << "In Base class" << endl; }
 8 };
 9 class Sub :public Base
10 {
11 public:
12     virtual void fn() { cout << "In Sub class" << endl; }
13 };
14 
15 void main()
16 {
17     Base bc;
18     Sub sc;
19 }

   對這個程序調試,截圖如下:

  

  由上圖可知,虛函數表_vfptr已經將父類Base和子類Sub的相同函數fn動態綁定到對應的類上,而且地址不一樣。

有關多態的值得注意的例子 

不用多態

  先看第一個程序:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Base
 5 {
 6 public:
 7     void fn() { cout << "In Base class" << endl; }
 8 };
 9 class Sub :public Base
10 {
11 public:
12     void fn() { cout << "In Sub class" << endl; }
13 };
14 
15 void test(Base& b)
16 {
17     b.fn();
18 }
19 
20 void main()
21 {
22     Base bc;
23     Sub sc;
24     test(bc);
25     test(sc);
26 }

  函數輸出如下:

  

  Sub對象sc在傳遞給test函數時,其另外添加的方法成員等(Sub)會被截掉,只剩Base部分,所以輸出是In Base class而不是In Sub class。

利用多態

  具體程序如下:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Base
 5 {
 6 public:
 7     virtual void fn() { cout << "In Base class" << endl; }
 8 };
 9 class Sub :public Base
10 {
11 public:
12     virtual void fn() { cout << "In Sub class" << endl; }
13 };
14 
15 void test(Base& b)
16 {
17     b.fn();
18 }
19 
20 void main()
21 {
22     Base bc;
23     Sub sc;
24     test(bc);
25     test(sc);
26 }

  程序輸出如下:

  

  這下程序就正確了。

 

  其實還有另一種方法可以達成跟虛函數一樣的效果,不過這並不是一種好的做法:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Base
 5 {
 6 public:
 7     void fn() { cout << "In Base class" << endl; }
 8 };
 9 class Sub :public Base
10 {
11 public:
12     void fn() { cout << "In Sub class" << endl; }
13 };
14 
15 void main()
16 {
17     Base bc;
18     Sub sc;
19     bc.fn();
20     sc.fn();
21 }

  具體輸出如下:

  

參考資料

  《深度探索C++對象模型》 

  《程序員的自我修養》

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM