• C++复习7.虚表的概念


    C++ 类的虚表 20130929

    关键技术:封装、继承、组合、虚函数、抽象基类、动态绑定、多态性等等

    1.首先整理一下在阿里巴巴面试遇到的函数虚表的问题。

             在C++中的Class中的函数式存储在Class数据机构的虚表中。每一个Class对应的所有的函数地址都会在Class的数据结构虚表中,每一个Class的对象在对象开始的地方都会有一个指针(计算机的位数一般是是32位)指向Class的函数虚表,函数虚表中每一个函数地址是按照在Class中声明的顺序。一般也是一个32bit的指针。

             C++的多态机制正是基于这种虚标实现的。

             首先看一个父类:

    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 Base的函数虚表中就是三个函数指针,分别指向这三个对应的函数,但是我们如何获取这些函数指针的值。

    我们声明一个对象Base base;

    这样在对象base中的首地址开始的一个机器长度的空间是一个指针,指向的是虚表的首地址,其中虚表的首地址第一个函数指针就是指向class中的第一个声明的函数。然后对该函数指针++ 操作便可以指向下一个函数,这也正是可以通过函数指针访问private函数的方式。(防君子不防小人)。

    一般继承中是没有函数覆盖的,所以在虚表中首先是父类的函数指针,后面是子类的函数指针。

    继承中有虚函数覆盖的情况,则子类中的Class虚表中对应的函数指针指向的是子类的函数地址,其他的不变。

    对于多重继承,则在对象的首地址前面有n个父类的虚表指针,分别指向对应父类的虚表,子类的函数地址存放在第一个父类的虚表中。这样是为了解决不同的父类指针指向同一个子类的对象的时候,可以调用实际对应的函数。

    当出现覆盖的情况的时候,就会将这些函数虚表中的虚函数换成对应子类的虚函数地址,在调用的时候会根据父类指针的类型,分别调用不同的父类的虚函数,而对于多态,则调用子类中的虚函数,在三个对应的虚表中,所有的改虚函数指针都会被修改指向子类的虚函数地址。

    对于子类实例中的虚函数表,是下面这个样子:

     


    我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

    Derive d;Base1 *b1 = &d;Base2 *b2 = &d;Base3 *b3 = &d;

    b1->f(); //Derive::f()

    b2->f(); //Derive::f()

    b3->f(); //Derive::f()

    b1->g(); //Base1::g()

    b2->g(); //Base2::g()

    b3->g(); //Base3::g()

           存在的一些不友好的地方:任何使用父类的指针调用子类没有覆盖父类成员函数的行为都会被编译器视为非法行为,出现编译出错的问题。但是可以在运行期间通过指针的方式访问虚函数表来达到违反C++语义的行为。

    正常的方式访问

    typedef void(*Fun)(void);//

    这是一个函数指针,使用Func 来表示指向一个函数,该函数的返回值类型是 void,参数是void。使用方式: Func pFunc = NULL ; 指向一个函数指针

        pFunc=(Fun)*((int*)*(int*)(&base));

    class Base{

    public:

        virtual void fun_a(void){

            cout << "base::fun_a" << endl;

        }

        virtual void fun_b(void){

            cout << "base::fun_b" << endl;

        }

        virtual void fun_c(void){

            cout << "base::fun_c" << endl;

        }

    private:

        int a;

    };

     

    class Derive: public Base{

    public:

        virtual void fun_a(void){

            cout << "derive::fun_a" << endl;

        }

        virtual void fun_b(void){

            cout << "derive::fun_b" << endl;

        }

        virtual void fun_d(void){

            cout << "derive::fun_d" << endl;

        }

    private:

        int b;

    };

     

    int main(){

     

        Derive derive;

     

        cout << "sizeof(derive):"<< sizeof(derive) << endl;

        cout << "derive  首地址:"  << &derive <<endl;

        ((Fun)*((int*)*(int*)(&derive) ))();

        ((Fun)*((int*)*(int*)(&derive) + 1 ))();

        ((Fun)*((int*)*(int*)(&derive) + 2 ))();

        ((Fun)*((int*)*(int*)(&derive) + 3 ))();

     

        Base base;

        Fun pFun = NULL;

        cout << "对象base的地址:" << &base << endl;

        cout << "第一个虚函数的地址:" <<  (int*)*(int*)(&base) << endl;

     

        pFun = (Fun)*((int*)*(int*)(&base));

        pFun();

        ((Fun)*((int*)*(int*)(&base) + 1))();

        ((Fun)*((int*)*(int*)(&base) + 2))();

        cout <<"sizeof(base):"<< sizeof(base) << endl;

        return 0;

    }

    防君子不防小人的实现:

           对于父类中的private and protected修饰的虚函数,在继承的时候,同样会存在在函数的虚表中,这样的话我们便可以通过访问虚函数的方式访问这些非public函数。

    typedef void(*Fun)(void);

    class Base{

    private:

        virtual void fun_a(void){

            cout << "base::fun_a" << endl;

        }

        virtual void fun_b(void){

            cout << "base::fun_b" << endl;

        }

        virtual void fun_c(void){

            cout << "base::fun_c" << endl;

        }

    private:

        int a;

    };

     

    class Derive: public Base{

    private:

        int b;

    };

     

    int main(){

        Derive derive;

        Base* base = &derive;

        Fun pFun = NULL;

        pFun = (Fun)*(int*)*(int*)&(*base);

        pFun();

        ((Fun)* ((int*)*(int*)&(*base) + 1 ) )();

        ((Fun)* ((int*)*(int*)&(*base) + 2 ) )();

        return 0;

    }

  • 相关阅读:
    UIAction 公共界面访问控制(拦截控制)
    MD5加密
    SVN的简单用法
    Spring AOP基础之JDK动态代理
    Spring中的事务管理模块基础
    Spring添加Junit4支持
    Spring里面dbcp连接池的配置和使用
    vue.js自定义指令详解
    vue input输入框联想
    export ,export default 和 import 区别 以及用法
  • 原文地址:https://www.cnblogs.com/hbhzsysutengfei/p/3409454.html
Copyright © 2020-2023  润新知