对于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还是有点问题,不是非常明白。