深入理解虚函数

一.  什么为虚函数

简而言之,在一个类中,前面带有virtual声明的成员函数就叫做虚函数,例如

class Base{
public:
    int x;
    int y;
public:
    Base(){
            x=1;
            y=2;
}
    virtual void Print(){         //该函数即为虚函数
            printf("%d %d
",x,y);
}
};

二.虚函数的间接调用

class Base            
{            
public:            
    void Function_1()            
    {            
        printf("Function_1...
");            
    }            
    virtual void Function_2()        //虚函数    
    {            
        printf("Function_2...
");            
    }            
};            

我们生成一个Base实例,通过对象访问函数,查看反汇编

Base base;            
            
base.Function_1();            
00401090 8D 4D FC             lea         ecx,[ebp-4]            
00401093 E8 9F FF FF FF       call        @ILT+50(Base::Function_1) (00401037)            
            
base.Function_2();            
00401098 8D 4D FC             lea         ecx,[ebp-4]            
0040109B E8 65 FF FF FF       call        @ILT+0(Base::Function_2) (00401005)            
            

我们可以观察到,Fn1与Fn2都是通过Call指令进行访问的,即代表着在编译期间,编译器就已经给这两个函数确定的地址,在CPU内留下硬地址,我们利用Call指令就可访问并执行函数

接着,我们用一个Base类型的指针来访问函数,查看反汇编得

Base* pb = &base;            
            
pb->Function_1();            
004010A6 8B 4D F8             mov         ecx,dword ptr [ebp-8]            
004010A9 E8 89 FF FF FF       call        @ILT+50(Base::Function_1) (00401037)            
            
pb->Function_2();            
004010AE 8B 4D F8             mov         ecx,dword ptr [ebp-8]     //将this指针放入ecx中,即结构体的首地址       
004010B1 8B 11                mov         edx,dword ptr [ecx]       //将结构体首地址的前四个字节取出放入到edx中     
004010B3 8B F4                mov         esi,esp                 
004010B5 8B 4D F8             mov         ecx,dword ptr [ebp-8]            
004010B8 FF 12                call        dword ptr [edx]           //将那四个字节代表的地址中的前四个字节拿出,当做地址进行call 

经过分析汇编代码可以得到,用指针访问虚函数时,函数的地址并不确定,而是通过各种转换而得到真正的函数地址并执行

总结:

通过对象调用时,virtual函数和普通函数都是E8 Call,即直接Call

通过指针调用时,普通函数为直接Call,virtual函数为FF Call,即间接Call

三.深入虚函数调用

class Base            
{            
public:            
    int x;            
    int y;            
    virtual void Function_1()            
    {            
        printf("Function_1...
");            
    }            
    virtual void Function_2()            
    {            
        printf("Function_2...
");            
    }            
};  
int main(){
  printf("%d",sizeof(Base));
}

当类中含有虚函数时,我们观察类的大小

去除两个局部变量的大小,我们发现虚函数的大小为4,而当我们去掉一个虚函数再测试大小时发现,虚函数大小仍为4.

于是去认真研究反汇编代码得

pb->Function_1();                            
0040D9E3 8B 4D F0             mov         ecx,dword ptr [ebp-10h]       //将结构体首地址放到ecx中                    
0040D9E6 8B 11                mov         edx,dword ptr [ecx]           //将首地址的前四个字节拿出放到edx中            
0040D9E8 8B F4                mov         esi,esp                            
0040D9EA 8B 4D F0             mov         ecx,dword ptr [ebp-10h]       //将结构体首地址放到ecx中                     
0040D9ED FF 12                call        dword ptr [edx]                            
                            
                            
pb->Function_2();                            
0040D9F6 8B 45 F0             mov         eax,dword ptr [ebp-10h]                            
0040D9F9 8B 10                mov         edx,dword ptr [eax]                            
0040D9FB 8B F4                mov         esi,esp                            
0040D9FD 8B 4D F0             mov         ecx,dword ptr [ebp-10h]                            
0040DA00 FF 52 04             call        dword ptr [edx+4]             //首地址前四个字节所代表的的地址里面存的值偏移+4               

发现带有虚函数的类中,首地址的前4个字节代表一个新的属性,这个地址指向一张表,即虚函数表,里面存储所有虚函数的地址

虚函数表

存储虚函数地址的表就叫做虚函数表

打印虚函数表

class Base                    
{                    
public:                    
    int x;                
    int y;                
    virtual void Function_1()                    
    {                    
        printf("Function_1...
");                    
    }                    
    virtual void Function_2()                    
    {                    
        printf("Function_2...
");                    
    }                    
    virtual void Function_3()                    
    {                    
        printf("Function_3...
");                    
    }                    
};                    
                    
                    
                    
void TestMethod()                    
{                    
    //查看 Sub 的虚函数表                
    Base base;                    
                    
    //对象的前四个字节就是虚函数表                
    printf("base 的虚函数表地址为:%x
",*(int*)&base);                
                    
    //通过函数指针调用函数,验证正确性                
    typedef void(*pFunction)(void);                    
                    
    pFunction pFn;                
                    
    for(int i=0;i<3;i++)                
    {                
        int temp = *((int*)(*(int*)&base)+i);            
        pFn = (pFunction)temp;            
        pFn();            
    }                
                    
                    
}                    

总结:

当类中有虚函数时,会多出一个属性,这个属性占4个字节

多出的一个属性是虚函数表的地址

四.深入研究虚函数表

struct Base                                
{                                
public:                                
    virtual void Function_1()                                
    {                                
        printf("Function_1...
");                                
    }                                
    virtual void Function_2()                                
    {                                
        printf("Function_2...
");                                
    }                                
    virtual void Function_3()                                
    {                                
        printf("Function_3...
");                                
    }                                
};                                
                                
                                
                                
int main(int argc, char* argv[])                                
{                                
    //查看 Base 的虚函数表                            
    Base base;                                
                                
    //对象的前四个字节就是虚函数表                            
    printf("Base 的虚函数表地址为:%x
",*(int*)&base);                            
                                
    //虚函数表中第1个函数地址                            
    printf("虚函数表中第1个函数地址:%x
",*((int*)(*(int*)&base)+0));                            
    //虚函数表中第2个函数地址                            
    printf("虚函数表中第2个函数地址:%x
",*((int*)(*(int*)&base)+1));                            
    //虚函数表中第3个函数地址                            
    printf("虚函数表中第3个函数地址:%x
",*((int*)(*(int*)&base)+2));                            
                                
    //通过函数指针调用函数,验证正确性                            
    typedef void(*pFunction)(void);                            
                                
    pFunction pFn;                            
    pFn = (pFunction)*((int*)(*(int*)&base)+0);                            
    pFn();                            
    pFn = (pFunction)*((int*)(*(int*)&base)+1);                            
    pFn();                            
    pFn = (pFunction)*((int*)(*(int*)&base)+2);                            
    pFn();                            
                                
    return 0;                            
}                                

下面来研究几个类型来加强对虚函数的理解

单继承无函数覆盖

struct Base                        
{                        
public:                        
    virtual void Function_1()                        
    {                        
        printf("Base:Function_1...
");                        
    }                        
    virtual void Function_2()                        
    {                        
        printf("Base:Function_2...
");                        
    }                        
    virtual void Function_3()                        
    {                        
        printf("Base:Function_3...
");                        
    }                        
};                        
struct Sub:Base                        
{                        
public:                        
    virtual void Function_4()                        
    {                        
        printf("Sub:Function_4...
");                        
    }                        
    virtual void Function_5()                        
    {                        
        printf("Sub:Function_5...
");                        
    }                        
    virtual void Function_6()                        
    {                        
        printf("Sub:Function_6...
");                        
    }                        
};                        
int main(int argc, char* argv[])                        
{                        
    //查看 Sub 的虚函数表                    
    Sub sub;                        
                        
    //对象的前四个字节就是虚函数表                    
    printf("Sub 的虚函数表地址为:%x
",*(int*)&sub);                    
                        
    //通过函数指针调用函数,验证正确性                    
    typedef void(*pFunction)(void);                        
                        
    pFunction pFn;                    
                        
    for(int i=0;i<6;i++)                    
    {                    
        pFn = (pFunction)*((int*)(*(int*)&sub)+i);                
        pFn();                
    }                    
                        
                        
    return 0;                    
}                        
                        

得到有一张虚函数表,且每个虚函数得到执行

单继承有函数覆盖

struct Base                    
{                    
public:                    
    virtual void Function_1()                    
    {                    
        printf("Base:Function_1...
");                    
    }                    
    virtual void Function_2()                    
    {                    
        printf("Base:Function_2...
");                    
    }                    
    virtual void Function_3()                    
    {                    
        printf("Base:Function_3...
");                    
    }                    
};                    
struct Sub:Base                    
{                    
public:                    
    virtual void Function_1()                    
    {                    
        printf("Sub:Function_1...
");                    
    }                    
    virtual void Function_2()                    
    {                    
        printf("Sub:Function_2...
");                    
    }                    
    virtual void Function_6()                    
    {                    
        printf("Sub:Function_6...
");                    
    }                    
};                    
int main(int argc, char* argv[])                    
{                    
    //查看 Sub 的虚函数表                
    Sub sub;                    
                    
    //对象的前四个字节就是虚函数表                
    printf("Sub 的虚函数表地址为:%x
",*(int*)&sub);                
                    
    //通过函数指针调用函数,验证正确性                
    typedef void(*pFunction)(void);                    
                    
    pFunction pFn;                
                    
    for(int i=0;i<6;i++)                
    {                
        int temp = *((int*)(*(int*)&sub)+i);            
        if(temp == 0)            
        {            
            break;        
        }            
        pFn = (pFunction)temp;            
        pFn();            
    }                
                    
    printf("%x
",*((int*)(*(int*)&sub)+6));                
                    
    return 0;                
}                    

 通过该输出我们可以得出一个结论,当有函数重载时,子类虚函数会覆盖基类虚函数,仅执行子类虚函数

多重继承有函数覆盖

struct Base1                            
{                            
public:                            
    virtual void Fn_1()                            
    {                            
        printf("Base1:Fn_1...
");                            
    }                            
    virtual void Fn_2()                            
    {                            
        printf("Base1:Fn_2...
");                            
    }                            
};                            
struct Base2                            
{                            
public:                            
    virtual void Fn_3()                            
    {                            
        printf("Base2:Fn_3...
");                            
    }                            
    virtual void Fn_4()                            
    {                            
        printf("Base2:Fn_4...
");                            
    }                            
};                            
struct Sub:Base1,Base2                            
{                            
public:                            
    virtual void Fn_1()                            
    {                            
        printf("Sub:Fn_1...
");                            
    }                            
    virtual void Fn_3()                            
    {                            
        printf("Sub:Fn_3...
");                            
    }                            
    virtual void Fn_5()                        
    {                            
        printf("Sub:Fn_5...
");                            
    }                            
};                            
int main(int argc, char* argv[])                            
{                            
    //查看 Sub 的虚函数表                        
    Sub sub;                            
                            
    //通过函数指针调用函数,验证正确性                        
    typedef void(*pFunction)(void);                            
                            
                            
    //对象的前四个字节是第一个Base1的虚表                        
    printf("Sub 的虚函数表地址为:%x
",*(int*)&sub);                        
                            
    pFunction pFn;                        
                            
    for(int i=0;i<6;i++)                        
    {                        
        int temp = *((int*)(*(int*)&sub)+i);                    
        if(temp == 0)                    
        {                    
            break;                
        }                    
        pFn = (pFunction)temp;                    
        pFn();                    
    }                        
                            
    //对象的第二个四字节是Base2的虚表                        
    printf("Sub 的虚函数表地址为:%x
",*(int*)((int)&sub+4));                        
                            
    pFunction pFn1;                        
                            
    for(int k=0;k<2;k++)                        
    {                        
        int temp = *((int*)(*(int*)((int)&sub+4))+k);                    
        pFn1 = (pFunction)temp;                    
        pFn1();                    
    }                        
                            
    return 0;                        
}                            

得出结论,当一个子类同时继承两个父类时,会产生两张虚函数表,且也是子虚函数会覆盖基类虚函数。

通过虚函数的使用,我们可以引出一句话:虚函数的本质就是动态绑定,即当函数被真正调用时,才能知道函数的地址。

总结:

只有virtual函数是动态绑定的

动态绑定还有一个名字:多态(多态的基础是虚函数)

原文地址:https://www.cnblogs.com/Virus-Faker/p/12360672.html