• C++ 虚函数表解析


    这篇博客“http://blog.csdn.net/haoel/article/details/1948051/”比较深度介绍了虚函数表解析。

    C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

    虚函数表

     

    C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

     

    这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

     

     

     

    我们可以看到下面几点:

    1)虚函数按照其声明顺序放于表中。

    2)父类的虚函数在子类的虚函数前面 

    我们可以看到:

    1)  每个父类都有自己的虚表。

    2)  子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

     

    这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

     

    二、访问non-public的虚函数

    另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

    如:

    class Base {

        private:

                virtual void f() { cout << "Base::f" << endl; }

    };

    class Derive : public Base{

    };

    typedef void(*Fun)(void);

    void main() {

        Derive d;

        Fun  pFun = (Fun)*((int*)*(int*)(&d)+0);

        pFun();

    }

    查看虚函数表:原博客说用vc IDE可以查看虚函数表,这里我就不实践了,

    另一种用vs2010 命令行 查看 虚函数表的方法(“http://blog.csdn.net/daydreamingboy/article/details/8982563”)。

    下面是一个关于多重继承的虚函数表访问的例程:

    #include <iostream>
    using namespace std;
     
    class Base1 {
    public:
                virtual void f() { cout << "Base1::f" << endl; }
                virtual void g() { cout << "Base1::g" << endl; }
                virtual void h() { cout << "Base1::h" << endl; }
     
    };
     
    class Base2 {
    public:
                virtual void f() { cout << "Base2::f" << endl; }
                virtual void g() { cout << "Base2::g" << endl; }
                virtual void h() { cout << "Base2::h" << endl; }
    };
     
    class Base3 {
    public:
                virtual void f() { cout << "Base3::f" << endl; }
                virtual void g() { cout << "Base3::g" << endl; }
                virtual void h() { cout << "Base3::h" << endl; }
    };
     
     
    class Derive : public Base1, public Base2, public Base3 {
    public:
                virtual void f() { cout << "Derive::f" << endl; }
                virtual void g1() { cout << "Derive::g1" << endl; }
    };
     
     
    typedef void(*Fun)(void);
     
    int main()
    {
                Fun pFun = NULL;
     
                Derive d;
                int** pVtab = (int**)&d;
     
                //Base1's vtable
                //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);
                pFun = (Fun)pVtab[0][0];
                pFun();
     
                //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);
                pFun = (Fun)pVtab[0][1];
                pFun();
     
                //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);
                pFun = (Fun)pVtab[0][2];
                pFun();
     
                //Derive's vtable
                //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);
                pFun = (Fun)pVtab[0][3];
                pFun();
     
                //The tail of the vtable
                pFun = (Fun)pVtab[0][4];
                cout<<pFun<<endl;
     
     
                //Base2's vtable
                //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
                pFun = (Fun)pVtab[1][0];
                pFun();
     
                //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
                pFun = (Fun)pVtab[1][1];
                pFun();
     
                pFun = (Fun)pVtab[1][2];
                pFun();
     
                //The tail of the vtable
                pFun = (Fun)pVtab[1][3];
                cout<<pFun<<endl;
     
     
     
                //Base3's vtable
                //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
                pFun = (Fun)pVtab[2][0];
                pFun();
     
                //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
                pFun = (Fun)pVtab[2][1];
                pFun();
     
                pFun = (Fun)pVtab[2][2];
                pFun();
     
                //The tail of the vtable
                pFun = (Fun)pVtab[2][3];
                cout<<pFun<<endl;
     
                return 0;
    }

    打开vs2010 命令提示:

    先更改目录到c++程序所在项目的目录。

    用cl /d1 reportAllClassLayout "vitual table.cpp"  或 cl /d1 reportSingleClassLayout*** "vitual table.cpp"(***可以是Base1,Base2,Base3,Derived)来打印***类的内存布局和虚函数表。

    命令提示 窗口 摘要

    D:Program Files (x86)Microsoft Visual Studio 10.0VCINCLUDExlocale(323) : wa
    rning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc
    class Base2 size(8):
    +---
    0 | {vfptr}
    +---

    Base2::$vftable@:
    | &Base2_meta
    | 0
    0 | &Base2::f
    1 | &Base2::g
    2 | &Base2::h

    Base2::f this adjustor: 0
    Base2::g this adjustor: 0
    Base2::h this adjustor: 0

    f:visual studio 2010Projectsvitual table>cl /d1 reportSingleClassLayoutDerive
    "vitual tablevitual table.cpp"
    用于 x64 的 Microsoft (R) C/C++ 优化编译器 16.00.30319.01 版
    版权所有(C) Microsoft Corporation。保留所有权利。

    vitual table.cpp
    D:Program Files (x86)Microsoft Visual Studio 10.0VCINCLUDExlocale(323) : wa
    rning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc
    class Derive size(24):
    +---
    | +--- (base class Base1)
    0 | | {vfptr}
    | +---
    | +--- (base class Base2)
    8 | | {vfptr}
    | +---
    | +--- (base class Base3)
    16 | | {vfptr}
    | +---
    +---

    Derive::$vftable@Base1@:
    | &Derive_meta
    | 0
    0 | &Derive::f      //derived的vptr 指针与 base1的vptr指针指向同一个地址空间。即$vftable@Base1@和base1的vftable 是同一个地址空间。
    1 | &Base1::g
    2 | &Base1::h
    3 | &Derive::g1

    Derive::$vftable@Base2@:
    | -8
    0 | &thunk: this-=8; goto Derive::f
    1 | &Base2::g
    2 | &Base2::h

    Derive::$vftable@Base3@:
    | -16
    0 | &thunk: this-=16; goto Derive::f
    1 | &Base3::g
    2 | &Base3::h

    Derive::f this adjustor: 0
    Derive::g1 this adjustor: 0

  • 相关阅读:
    WPF 对Border 边框进行投影
    WPF 自定义可拖动标题栏
    WPF 静态资源(StaticResource)和动态资源(DynamicResource)
    SQL数据分组后取第一条数据——PARTITION BY
    驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“The server selected protocol version TLS10 is not accepted by client
    Windows 安装 MySQL 5.7 x64 位
    kubeadm init port is in use
    SQL Server 2016 安装
    驼峰下划线互转
    使用 Kubeadm 部署 Kubernetes(K8S) 安装
  • 原文地址:https://www.cnblogs.com/xaf-dfg/p/3781684.html
Copyright © 2020-2023  润新知