谈虚函数表

2021-08-27  本文已影响0人  404Not_Found

虚函数的位置

虚函数并没有固定位置,应该与编译器有关。在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
上一篇下一篇

猜你喜欢

热点阅读