C C 概念 VC虛函式布局引發的問題

2021-09-01 20:38:43 字數 3579 閱讀 3568

在網上看到乙個非常熱的帖子,裡面是這樣的乙個問題:

在列印的時候發現pfun的位址和 &(base::f)的位址竟然不一樣太奇怪了?經過一番深入研究,終於把這個問題弄明白了。下面就來一步步進行剖析。

根據vc的虛函式的布局機制,上述的布局如下:

然後我們再細細的分析第一種方式:

fun pfun = (fun)*((int*)*(int*)(d)+0);

d是乙個類物件的位址。而在32位機上指標的大小是4位元組,因此*(int*)(&d)取得的是vfptr,即虛表的位址。從而*((int*)*(int*)(&d)+0)是虛表的第1項,也就是base::f()的位址。事實上我們得到了驗證,程式執行結果如下:

這說明虛表的第一項確實是虛函式的位址,上面的vc虛函式的布局也確實木有問題。

但是,接下來就引發了乙個問題,為什麼&(base::f)和pfun的值會不一樣呢?既然pfun的值是虛函式f的位址,那&(base::f)又是什麼呢?帶著這個問題,我們進行了反彙編。

printf("&(base::f): 0x%x \n", &(base::f));

00401068 mov edi,dword ptr [__imp__printf (4020d4h)]

0040106e push offset base::`vcall'' (4013a0h)

00401073 push offset string "&(base::f): 0x%x \n" (40214ch)

00401078 call edi

printf("&(base::g): 0x%x \n", &(base::g));

0040107a push offset base::`vcall'' (4013b0h)

0040107f push offset string "&(base::g): 0x%x \n" (402160h)

00401084 call edi

那麼從上面我們可以清楚的看到:

base::f 對應於base::`vcall'' (4013a0h)

base::g對應於base::`vcall'' (4013b0h)

那麼base::`vcall''和base::`vcall''到底是什麼呢,繼續進行反彙編分析

base::`vcall'':

004013a0 mov eax,dword ptr [ecx]

004013a2 jmp dword ptr [eax]

......

base::`vcall'':

004013b0 mov eax,dword ptr [ecx]

004013b2 jmp dword ptr [eax+4]

第一句中, 由於ecx是this指標, 而在vc中一般虛表指標是類的第乙個成員, 所以它是把vfptr, 也就是虛表的位址存到了eax中. 第二句

相當於取了虛表的某一項。對於base::f跳轉到base::`vcall'',取了虛表的第1項;對於base::g跳轉到base::`vcall'',取了虛表第2項。由此都能夠正確的獲得虛函式的位址。

由此我們可以看出,vc對此的解決方法是由編譯器加入了一系列的內部函式"vcall". 乙個類中的每個虛函式都有乙個唯一與之對應的vcall函式,通過特定的vcall函式跳轉到虛函式表中特定的表項。

更深一步的進行討論,考慮多型的情況,將**改寫如下:

列印的時候表現出來了多型的性質:

分析可知原因如下:

這是因為類derive的虛函式表的各項對應的值進行了改寫(rewritting),原來指向based::f()的位址變成了指向derive::f(),原來指向based::g()的位址現在編變成了指向derive::g()。

反彙編**如下:

printf("&(derive::f): 0x%x \n", &(derive::f));

00401086 push offset base::`vcall'' (4013b0h)

0040108b push offset string "&(derive::f): 0x%x \n" (40217ch)

00401090 call esi

printf("&(derive::g): 0x%x \n", &(derive::g));

00401092 push offset base::`vcall'' (4013c0h)

00401097 push offset string "&(derive::g): 0x%x \n" (402194h)

0040109c call esi

因此雖然此時derive::f依然對應base::`vcall'',而derive::g依然對應base::`vcall'',但是由於每個類有乙個虛函式表,因此跳轉到的虛表的位置也發生了改變,同時因為進行了改寫,虛表中的每個slot項的值也不一樣。

稍微總結一下:

在vc中有兩種方法呼叫虛函式,一種是通過虛表,另外一種是通過vcall thunk的方式

通過虛表的方式

base *d = new derive;

d->f();

004115fa mov eax,dword ptr [d]

004115fd mov edx,dword ptr [eax]

004115ff mov esi,esp

00411601 mov ecx,dword ptr [d]

00411604 mov eax,dword ptr [edx]

00411606 call eax

00411608 cmp esi,esp

0041160a call @ilt+470(__rtc_checkesp) (4111dbh)

這種方式的應用環境是通過類物件的指標或引用來呼叫虛函式

通過vcall thunk的方式:

typedef void (base::* func1)( void );

base *d = new derive;

func1 pfun1 = &base::f;

(d->*pfun1)();

004115a9 mov dword ptr [pfun1],offset base::`vcall'' (4110c3h)

004115b0 mov esi,esp

004115b2 lea ecx,[d]

004115b5 call dword ptr [pfun1]

004115b8 cmp esi,esp

004115ba call @ilt+460(__rtc_checkesp) (4111d1h)

is uploaded file函式引發的問題

起因 在利用moophp的乙個專案中,接到使用者反饋說其所有客戶不能上傳檔案,都返回失敗。經過排查發現是php中的is uploaded file函式在 搗鬼。細節分析 在正常情況下,通過php 上傳檔案 需要通過is uploaded file函式來判斷檔案是否是通過 http post 上傳的,...

C 虛函式表的布局

針對虛函式表的結構與布局,寫了乙個程式驗證一下 首先看單一繼承的情況 class base virtual void y virtual void z class derive public base,public base2 重寫 virtual void y1 virtual void y 重寫...

理解虛基類 虛函式與純虛函式的概念

虛基類在說明其作用前先看一段 從 中可以看出類b c都繼承了類a的ivalue成員,因此類b c都有乙個成員變數ivalue 而類d又繼承了b c,這樣類d就有乙個重名的成員 ivalue 乙個是從類b中繼承過來的,乙個是從類c中繼承過來的 在主函式中呼叫d.ivalue 因為類d有乙個重名的成員i...