美文网首页
谈虚函数表

谈虚函数表

作者: 404Not_Found | 来源:发表于2021-08-27 10:28 被阅读0次
    • 作者: 雪山肥鱼
    • 时间:20210825 22:50
    • 目的: 理解虚函数表

    虚函数的位置

    虚函数并没有固定位置,应该与编译器有关。在vs2017 和 g++ 下,处于对象开头

    #include <iostream>
    using namespace std;
    
    class A
    {
    public:
        int i; //4字节
        virtual void testfunc() {}  //虚函数,vptr4字节。
    };
    
    int main()
    {
        //虚函数表指针位置分析
        //类:有虚函数,这个类会产生一个虚函数表。
        //类对象,有一个指针,指针(vptr)会指向这个虚函数表的开始地址。
        A aobj;
        int ilen = sizeof(aobj);
        cout << ilen << endl;  //8字节
    
        char *p1 = reinterpret_cast<char *>(&aobj); //类型转换,硬转 &aobj这是对象aobj的首地址。
        char *p2 = reinterpret_cast<char *>(&(aobj.i));
        if (p1 == p2) //说明aobj.i和aobj的位置相同,说明i在对象aobj内存布局的上边。虚函数表指针vptr在下边
        {
            cout << "虚函数表指针位于对象内存的末尾" << endl;
        }
        else
        {
            cout << "虚函数表指针位于对象内存的开头" << endl;
        }
              
    
    
        return 1; 
    }
    

    输出均在对象内存开头

    继承关系下的虚函数表

    #include <iostream>
    #include <stdio.h>
    
    using namespace std;
    
    class Base
    {
        public:
            virtual void f() {cout << "Base::f()" <<endl; }
            virtual void g() {cout << "Base::g()" <<endl; }
            virtual void h() {cout << "Base::h()" <<endl; }
    };
    
    class Derive :public Base
    {
        public:
            virtual void g() {cout << "Base::g()" <<endl; }
    };
    
    
    int main(int argc,char **argv)
    {
        cout << sizeof(Base) <<endl;
        cout << sizeof(Derive)<< endl;
    
        cout << "---------Derive-----------" << endl;
    
        
        Derive *d = new Derive();
        
        long * pvptr = (long*)d;
        long * vptr = (long*)*pvptr; 
        
        for (int i = 0; i <= 4; i++) 
        {
            printf("vptr[%d] = 0x:%p\n", i, vptr[i]);
    
        }
    
        typedef void (*Func)(void);
    
        Func f = (Func)vptr[0]; 
        Func g = (Func)vptr[1];
        Func h = (Func)vptr[2];
    
        f();
        g();
        h();
    
        cout << "------Base-----------" << endl;
    
        Base *dpar = new Base();
        long *pvptrpar = (long *)dpar;
        long *vptrpar = (long *)(*pvptrpar);
    
        for (int i = 0; i <= 4; i++) //循环5次;
        {
            printf("vptr Base[%d] = 0x:%p\n", i, vptrpar[i]);
    
        }
    
        Func fpar = (Func)vptrpar[0]; 
        Func gpar = (Func)vptrpar[1];
        Func hpar = (Func)vptrpar[2];
    
        fpar(); 
        gpar();
        hpar();
        return 0;
    }
    
    
    运行结果.png

    父子函数虚函数表的关系:


    父子虚函数表的关系.png

    代码说明:

    1. 取出函数指针的操作,只是想拿到虚函数表的每隔元素,注意是vtable, 而不是varray, 是table, 不是数组,说明里面的函数指针类型是可以不同的。
      所以用long 去取,只是为了步长是4, 拿到元素而已。
    2. 父类一张表A, 子类一张表B, 子类自定义的虚函数,会更新虚函数表里的函数地址,其他不变,继承下来。
    3. 如果子类没有重新任何虚函数,则会拷贝一份父类的虚函数表。两者内容相同,但是在内存的不同位置。
    4. 超出虚函数表的内容暂定为不可知

    虚表在继承关系之间赋值的情况分析

    #include <iostream>
    using namespace std;
    
    class Base
    {
    public:
        virtual void f() { cout << "Base::f()" << endl; }
        virtual void g() { cout << "Base::g()" << endl; }
        virtual void h() { cout << "Base::h()" << endl; }
    };
    
    class Derive :public Base
    {
        virtual void g() { cout << "Derive::g()" << endl; }
    };
    
    typedef void(*Func)(void);
    
    int main(int argc, char ** argv)
    {
        Derive derive;
        long * pvfbderive = (long*)&derive;
        long * pvptrderive = (long*)(*pvfbderive);
        
        //pvptrderive = 0x00289b6c {Project1.exe!void(* Derive::`vftable'[4])()} {2625911}
        Func f1 = (Func)pvptrderive[0]; //f1 = 0x00281177 {Project1.exe!Base::f(void)}
        Func f2 = (Func)pvptrderive[1]; //f2 = 0x002812cb {Project1.exe!Derive::g(void)}
        Func f3 = (Func)pvptrderive[2]; //f3 = 0x002812df {Project1.exe!Base::h(void)}
        Func f4 = (Func)pvptrderive[3];
        Func f5 = (Func)pvptrderive[4];
        f1();
        f2();
        f3();
    
        Base base = derive;//生成base对象,用derive 初始化base对象,编译器直接切割derive,把属于base的对象的内容的内容拷贝给对象base。
                        //  子对象的 虚函数表指针的值,并没有覆盖 base对象的 虚函数表指针值
        long * pvfbBase = (long*)&base;
        long * pvptrBase = (long*)(*pvfbBase);
    
        //pvptrBase = 0x00289b34 {Project1.exe!void(*Base::`vftable'[4])()} {2625911}
        Func b1 = (Func)pvptrBase[0];//b1 = 0x00281177 {Project1.exe!Base::f(void)}
        Func b2 = (Func)pvptrBase[1];//b2 = 0x0028114f {Project1.exe!Base::g(void)}
        Func b3 = (Func)pvptrBase[2];//b3 = 0x007812df {Project1.exe!Base::h(void)}
        Func b4 = (Func)pvptrBase[3];
        Func b5 = (Func)pvptrBase[4];
    
        Base * pBase = new Derive();
        long * ppvfbBase = (long*)pBase;
        long * ppvptrBase = (long*)(*ppvfbBase);
    
        Func bb1 = (Func)ppvptrBase[0];//bb1 = 0x0046119f {Project1.exe!Base::f(void)}
        Func bb2 = (Func)ppvptrBase[1];//bb2 = 0x00461311 {Project1.exe!Derive::g(void)}
        Func bb3 = (Func)ppvptrBase[2];//bb3 = 0x00321325 {Project1.exe!Base::h(void)}
    
        Base & rBase = derive;
        long * rpvfbBase = (long*)&rBase;
        long * rpvptrBase = (long*)(*rpvfbBase);
    
        Func rb1 = (Func)rpvptrBase[0]; //rb1 = 0x004d119f {Project1.exe!Base::f(void)}
        Func rb2 = (Func)rpvptrBase[1]; //rb2 = 0x004d1311 {Project1.exe!Derive::g(void)}
        Func rb3 = (Func)rpvptrBase[2]; //rb3 = 0x004d1325 {Project1.exe!Base::h(void)}
        
        b2();
    
        return 0;
    

    父类对象 = 子类对象

    由注释结果可知,这种情况,子类对象的虚函数表指针并没有覆盖父类对象的虚函数表指针,父类对象自己用自己的虚函数表
    所以此时并不存在多态说法

    父类对象指针 = 子类对象地址

    子类虚函数表指针覆盖覆盖 父类虚函数表指针

    父类对象引用 = 子类对象

    子类虚函数表指针覆盖覆盖 父类虚函数表指针

    多重继承中的虚函数表分析

    using namespace std;
    
    class Base1 {
    public:
        virtual void f()
        {
            cout << "base1::f()" << endl;
        }
    
        virtual void g()
        {
            cout << "base1::g()" << endl;
        }
    };
    
    class Base2 {
    public:
        virtual void h()
        {
            cout << "base2::f()" << endl;
        }
    
        virtual void i()
        {
            cout << "base2::g()" << endl;
        }
    };
    
    class Derived:public Base1, public Base2
    {
    public:
        virtual void f() 
        {
            cout << "derived::f()" << endl;
        }
        virtual void i() 
        {
            cout << "derived::i()" << endl;
        }
    
        virtual void mh()
        {
            cout << "derived::mh()" << endl;
        }
    
        virtual void mi()
        {
            cout << "derived::mi()" << endl;
        }
    
        virtual void mj()
        {
            cout << "derived::mj()" << endl;
        }
    };
    
    int main(int argc, char ** argv)
    {
        cout << sizeof(Base1) << endl;
        cout << sizeof(Base2) << endl;
        cout << sizeof(Derived) << endl;
    
        Derived ins;
        Base1 &b1 = ins;
        Base2 &b2 = ins;
        Derived &d = ins;
    
        typedef void(*Func)(void);
        long * pderived1 = (long*)(&ins);
        long * vptr1 = (long*)(*pderived1);
    
        long * pderived2 = pderived1 + 1;
        long * vptr2 = (long*)(*pderived2);
    
        Func f1 = (Func)vptr1[0]; //f1 = 0x0082122b {Project1.exe!Derived::f(void)}
        Func f2 = (Func)vptr1[1]; //f2 = 0x008213a7 {Project1.exe!Base1::g(void)}
        Func f3 = (Func)vptr1[2]; //f3 = 0x00821177 {Project1.exe!Derived::mh(void)}
        Func f4 = (Func)vptr1[3]; //f4 = 0x00821438 {Project1.exe!Derived::mi(void)}
        Func f5 = (Func)vptr1[4]; //f5 = 0x00821325 {Project1.exe!Derived::mj(void)}
        Func f6 = (Func)vptr1[5]; //f6 = 0x00000000
        Func f7 = (Func)vptr1[6]; //f7 = 0x0082a938 {Project1.exe!const Derived::`RTTI Complete Object Locator'{for `Base2'}
    
        Func f11 = (Func)vptr2[0]; //f11 = 0x008210dc {Project1.exe!Base2::h(void)}
        Func f12 = (Func)vptr2[1]; //f12 = 0x00821131 {Project1.exe!Derived::i(void)}
        Func f13 = (Func)vptr2[2]; //f13 = 0x00000000
        Func f14 = (Func)vptr2[3]; //f14 = 0x69726564
        
        b1.f();
        b2.i();
    
        d.f();
        d.i();
        d.mh();
        d.g();//故意没有重新 base1的虚函数 g
    
    
        return 0;
    }
    

    由上述结果可知

    1. 子类 继承两个父类,则会有两个虚函数表指针,指向两个虚函数表
    2. 子类 和 第一个父类 共用一个虚函数表,也就是说,这个表里面有父类一(重写或未重写)的表, 也有 保存了子类自己的虚函数
    3. 对于未被重写的 虚函数 g(), 子类依旧使用父类的虚函数,因为函数表中的该项指向的函数不变。
    多继承下的虚函数表.png

    虚函数表和虚函数指针是何时创建的

    vs2017 下可以在开发人员模式先用以下命令查看对象结构:

    cl /dl reportSingleClassLayoutDerived main.cpp
    
    结构.png

    对象结构与上一节中的对象分析截图相同

    linux 下 用

    g++ -fdump-class-hierarchy -fsyntax-only main.cpp
    
    结构.png
    • 对象什么时候创建出来,虚函数指针就何时创建
      实际上通过之前章节了解到,对象创建调用构造函数,会自己往构造函数塞代码,给vptr赋值
    • 虚函数表的创建时机
      在编译阶段就已经准备好了,其实就在代码段啦。

    相关文章

      网友评论

          本文标题:谈虚函数表

          本文链接:https://www.haomeiwen.com/subject/odxyiltx.html