C++中的虚函数表

来源:互联网 发布:铃声剪辑器 mac 编辑:程序博客网 时间:2024/04/27 17:59

 本文摘自(www.cnblogs.com/wirelesser/archive/2008/03/09/1097463.html)

大家都知道C++中的虚函数的实现一般是通过虚函数表(C++规范并没有规定具体用哪种方法,但大部分的编译器厂商都选择此方法),下面通过虚函数表来看看C++中虚函数的实现

class A

{

public:

       int ai;

       virtual void func(){cout<<"A-func"<<endl;}

};

class AA:public A

{

public:

       void func(){cout<<"AA-func"<<endl;}

};

int main( )

{

       AA *paa = new AA;

       A *pa = new A;

}  

可以看到paa指向的AA对象中,其子对象A的虚函数表(vftb)地址为0x0047e69c,正好是AA对象的首32

倘若将类定义改成:

class A

{

public:

       int ai;

       virtual void func(){cout<<"A-func"<<endl;}

};

class AA:public A

{

public:

       //void func(){cout<<"AA-func"<<endl;} 去掉AA中这个虚函数的override.

};

那么可以看到paa中的虚函数表中的第一个项(即从A继承而来的func),和pa中虚函数表的第一个项(A中的func),都指向同一个地方(0x004010f5),可以得出这样一个结论:当派生类没有覆盖(override)基类中的虚函数时,那么这个虚函数就会指向基类的虚函数实现。在C#中也有类似的概念,C#中的override关键字即表明派生类需要改写基类中虚函数项的指向。



在派生类中新增一个
virtual func 虚函数

class A

{

public:

     int ai;

     virtual void func(){cout<<"A-func"<<endl;}

     virtual void func2(){cout<<"A-func2"<<endl;}

};

class AA:public A

{

public:

void func(){cout<<"AA-func"<<endl;}
virtual void aa_func(){cout<<"AA-aa_func"<<endl;}

};

对于这份代码,VS2005debugger并没有正确反映出AAaa_func的位置(从图中看到vfptr虚函数表只有2),但是,从图上仍然可以看出paa->aa_func占用了paa->__vfptr表中第三项位置(vfptr+8即表的第三项,每个表项的偏移为4个字节)。另外,我们再次看到如果派生类没有覆盖基类的虚函数,那么,它就和基类指向同一个函数体(两者的func2都指向了A::func2)


让我们回忆一下上一篇关于
C++的类型转换的过程:

AA *paa = new AA;

这时候paa指向了AA的一个实例(instance)

A *pa = new AA;

这时候pa指向的仍然是AA的这个实例

因此,pa->func paa->func都指向了AA实例vftb的第一项也就是AA:func(),为什么指针可以呈现出多态的原因也就不言而喻了。(对于引用也是同样的道理)

再来看看非虚函数和虚函数的汇编代码:

class A

{

public:

     int ai;

     virtual void func(){cout<<"A-func"<<endl;} //看看有(没有)virtual的汇编代码

};

class AA:public A

{

public:

};

非虚函数:

     paa->A::func();

004010C6 mov         ecx,dword ptr [paa]

004010C9 add         ecx,4    //将paa对象压栈(偏移4是因为首4个字节保存的是vftb虚函数表)

004010CC call        A::func (401046h)

虚函数:

     paa->func();

00401230 mov         edx,dword ptr [paa] //paa对象->edx

00401233 mov         eax,dword ptr [edx] //vftb->eax

00401235 mov         ecx,dword ptr [paa] //paa对象->ecx

00401238 mov         edx,dword ptr [eax] //vftb的第一项->edx,也就是func所在的位置

0040123A call        edx

经过一系列复杂的计算,最后edx寄存器里存放的就是func的正确地址,这也叫做late-binding(迟绑定)

最后看看虚函数导致对象的内存布局

当基类中没有虚函数的时候,自然基类也就没有虚函数表,而当基类中有虚函数的时候,编译器产生一个虚函数表,派生类就使用其基类子对象中的虚函数表.

class A

{

public:

     int ai;

     virtual void func2(){cout<<"A-func2"<<endl;}

    

};

class AA:public A

{

public:

     void func(){cout<<"AA-func"<<endl;}

    virtual void aa_func(){cout<<"AA-aa_func"<<endl;}

};


Vftb的第一项是A中的func2(),而第二项是AA中的aa_func

原创粉丝点击