zoukankan      html  css  js  c++  java
  • 深入理解虚函数

    一.  什么为虚函数

    简而言之,在一个类中,前面带有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函数是动态绑定的

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

  • 相关阅读:
    如何设计web系统的监控
    RedisCluster的rename机制失败报错,解决又是数据倾斜问题
    学习大数据基础资源收集与分享
    用过滤器实现日志记录
    HttpClient 教程
    【公告】
    【2020赛季训练实录】
    【BZOJ5415&UOJ393】归程(Kruskal重构树,最短路)
    【BZOJ3545&BZOJ3551】Peaks(kruskal重构树,主席树,dfs序)
    【CF1263E】Editor(线段树,栈)
  • 原文地址:https://www.cnblogs.com/Virus-Faker/p/12360672.html
Copyright © 2011-2022 走看看