對于vft(vitrual function table或是vitrual method table)和vptr(vitrual pointer)做一些總結。
當類中有虛函數的時候才會建立vft,這個表里面按照順序(從0開始)和類里面的虛函數做出對應。
如果一個類里面有虛函數(就是有vft),那它就有個vptr。vptr是一個存在在類中的一個隱含指針,這個指針指向的是vft這個表。
當調用類中的某個虛函數的時候,就通過這個指針去找vft里面對應的函數,然后拿來調用。
class Base
{
public:
FunctionPointer *__vptr;
virtual void function1() {};
virtual void function2() {};
};
class D1: public Base
{
public:
virtual void function1() {};
};
class D2: public Base
{
public:
virtual void function2() {};
};
這里顯示的把vptr顯示出來,但實際上是看不見的。

注意d1和d2的vptr是繼承自base的
int main()
{
D1 cClass;
Base *pClass = &cClass;
pClass->function1();
}
上面的代碼之所以可以執行,是因為:pClass指針指向的僅僅是cClass中屬于Base的部分(因為Base是D1的父類),因為vptr原本是在Base中的(雖然看不見),所以pClass是可以調用vptr的。再由于繼承的關系,此時的vptr處于D1類中,它指向的是D1的vft,所以pClass->funtion1()
這句可以執行成功。
說一個復雜的情況:
class A
{
public:
virtual void a();
};
class B : public A
{
public:
virtual void a();
virtual void c();
virtual void f();
};
class C : public A
{
public:
virtual void e();
};
class D : public B, public C
{
public:
void a();
void g();
}:
這里就有點問題,如果單純的vft中按照函數順序的話。在D這個類中,來自B和來自C這兩個類中的B::c函數和C::e函數在他們的類中所在的位置從文本上看都是第二的位置(C類還有個A中繼承的a函數)。那么對于D來說有同一個位置有兩個函數,這時vft必然不能正常實現。在C++中其實D這個類有兩個vft,一個基于B建立,一個基于C建立。當然這種情況下只有在多繼承時才出現。
那么在實際運行時,首先要把vft的地址(也就是vptr的值)放到寄存器里面,然后要確定用的是哪個類的vft(對于D這個類來說),接著再索引這個函數表找到函數,最后才根據地址執行函數。
也就只對于多繼承這種復雜的情況下,才有上面這么多步驟。對于單繼承,只用找到表,找到索引,找到函數,調用即可。GCC的thunk在建立vft的時候就確定了到底這個函數是在哪個對象
load [object_reg+#VFToffset], table_reg
load [table_reg+#deltaOffset], delta_reg
load [table_reg+#numOffset], method_reg
add object_reg, delta_reg, object_reg
call method_reg
上面的匯編取自一篇論文,VFToffset就是vft的地址偏移,deltaOffset就是多繼承產生的偏移(選哪個類,B還是C),numOffset就是函數偏移。省去的就是2、4這兩條語句,thunk把這步做了。
關于thunk還是有點問題,不是非常明白。