C 從虛函式表的底層來看虛函式呼叫問題

2021-07-29 01:37:28 字數 2724 閱讀 8969

原本以為自己對虛函式掌握的還可以,結果前幾天面試的時候被問了乙個基類指標操作派生類物件的時候,構造和析構函式裡呼叫虛函式的時候,呼叫的是基類的還是派生類的。結果就給答錯了,我當時覺得乙個類裡面的虛函式表覆蓋的函式已經只能指向派生類的。因為當時我覺得,面試官提醒在構造和析構函式呼叫的時候,派生類還不存在或者已經被析構了,還怎麼能呼叫呢?

於是我就打算再從底層理解一下虛函式問題

一如上面面試官所說,在構造和析構函式呼叫的時候,派生類還不存在或者已經被析構了,是不能呼叫派生類的虛函式的。所以這道題的標準答案是:

在構造/析構函式函式中呼叫虛函式,是不會出現多型的現象,而是呼叫其自身【基類】的虛函式

但是我當時疑惑的事情在於,因為虛函式的呼叫需要用到虛函式表,而在派生類中,其虛函式表中派生類相應的虛函式位址已經覆蓋了基類的對應的虛函式位址,那麼如何才能夠找到已經被覆蓋的虛函式位址的呢?

我查了一下各種資料,發現在建構函式/析構函式中呼叫虛函式的例子能夠很好的說明這個問題。

**可以去原帖中看最後一段**

因為類例項位址裡面,虛函式表的指標時存在開頭的位置,所以int* vt = (int*)*((int*)this);這一步是取得其虛函式表的指標。

在生成派生類的物件時,是先呼叫了基類的建構函式,再呼叫派生類的建構函式,析構函式的呼叫跟建構函式的呼叫順序是相反的,它從最派生類的析構函式開始的。也就是說當基類的析構函式執行時,派生類的析構函式已經執行。

所以在基類和派生類的建構函式和析構函式都呼叫乙個列印當前this指標位址【表示這個類例項的位址】,答應指向虛函式表的指標的指向位置。

#include 

class base

virtual ~base()

void printbase()

virtual

void foo()

};class derive : public base

virtual ~derive()

void printderive()

virtual

void foo()

};int main()

輸出結果如下

address of base: 004a8868

address of base vtable: 00c18be0

call foo by vt -> base

address of derive: 004a8868

address of derive vtable: 00c18d54

call foo by vt -> derive

address of derive: 004a8868

address of derive vtable: 00c18d54

call foo by vt -> derive

address of base: 004a8868

address of base vtable: 00c18be0

call foo by vt -> base

可以看到在建立new derive();類的過程中,在呼叫不論是基類還是派生類的建構函式和析構函式時,this指標時不會發生變化的,也就是說這個類本身位址是沒有變化的。但是其虛函式表指標指向的位址卻發生了變化:

在呼叫基類的建構函式和析構函式時,其虛函式表指標指向的是基類的虛函式表位址,如上面程式的00c18be0

而在呼叫派生類的建構函式和析構函式時,其虛函式表指標指向的是派生類的虛函式表位址,如上面程式的004a8868

所以說在類生成和析構的過程中,其虛函式表的指標時不斷在發生變化的,所以就有了如下的兩個問題:

深入c++虛表中的說法:

擁有虛函式的類會有乙個虛表,而且這個虛表存放在類定義模組的資料段中。模組的資料段通常存放定義在該模組的全域性資料和靜態資料,這樣我們可以把虛表看作是模組的全域性資料或者靜態資料

也就是說虛函式表是在程式一開始執行的時候聚初始化好了的,每個類裡面虛函式表中都有哪些內容都是已經訂好的。類的虛表會被這個類的所有物件所共享。類的物件可以有很多,但是他們的虛表指標都指向同乙個虛表。

而具體類的例項中的虛表指標的時候,是在類生成過程中動態繫結的,詳見下一條:

按照中的反彙編的得到的彙編**我們可以看到,類例項中的虛表指標是在類呼叫建構函式的時候完成初始化的,只不過是在進入建構函式函式體之前就完成了初始化。

這也就是為什麼建構函式不能是虛函式的原因,因為虛函式的呼叫需要涉及到虛表指標,而在構造函式呼叫之前,虛表指標還沒有完成初始化,就沒法呼叫虛的建構函式。

繼續說我們的這塊的初始化內容,在建構函式進入函式體之前,進行虛表的初始化,此時需要講虛表指標初始化為當前類的虛表位址,即在基類呼叫建構函式的時候,會把基類的虛表位址賦值給虛表指標,而如果進行到子類的建構函式時,就把子類的虛表位址賦值給虛表指標。

所以就有了上面那個程式中,乙個派生類建立過程中,其虛表指標在不斷變化的原因。

而在析構函式中剛好相反,可以認為編譯器在子類的析構函式的末尾將虛表指標指向了基類,然後緊接著插入了基類的析構函式。【這個部分存疑,我沒有找到作證我的觀點例子】

在建構函式/析構函式中呼叫虛函式

深入c++虛表

從彙編層面深度剖析c++虛函式

虛函式表指標,虛函式表

對c 了解的人都應該知道虛函式 virtual function 是通過一張虛函式表 virtual table 來實現的。簡稱為v table。在這個表中,主是要乙個類的虛函式的位址表,這張表解決了繼承 覆蓋的問題,保證其容真實反應實際的函式。這樣,在有虛函式的類的例項中這個表被分配在了 這個例項...

C 虛函式與虛函式表

概念 虛函式 virtual function 是通過一張虛函式表 virtual table 來實現的,簡稱為v table。學習虛函式的作用 理解 c 實現多型的機制 解決了繼承 覆蓋的問題。以下摘抄自 http www.cppblog.com xczhang archive 2008 01 2...

C 虛函式指標虛函式表

c 的多型可以分為靜態多型和動態多型。函式過載和運算子過載實現的多型屬於靜態多型,而通過虛函式可以實現動態多型。實現函式的動態聯編其本質核心則是虛表指標與虛函式表。1.虛函式與純虛函式區別 1 虛函式在子類裡面也可以不過載的 但純虛必須在子類去實現 2 帶純虛函式的類叫虛基類也叫抽象類,這種基類不能...