zoukankan      html  css  js  c++  java
  • C++虚函数表分析

    先看代码:

    复制代码
    #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{
    public:
        void g() {cout<<"derive::g"<<endl;}
    };
    
    //可以稍后再看
    int main () {
        cout<<"size of Base: "<<sizeof(Base)<<endl;
    
        typedef void(*Func)(void);
        Base b;
        Base *d = new Derive();
    
        long* pvptr = (long*)d;
        long* vptr = (long*)*pvptr;
        Func f = (Func)vptr[0];
        Func g = (Func)vptr[1];
        Func h = (Func)vptr[2];
    
        f();
        g();
        h();
    
        return 0;
    }
    复制代码

        都知道C++中的多态是用虚函数实现的: 子类覆盖父类的虚函数, 然后声明一个指向子类对象的父类指针, 如Base *b = new Derive();
    当调用b->f()时, 调用的是子类的Derive::f()。 
    这种机制内部由虚函数表实现,下面对虚函数表结构进行分析,并且用GDB验证。
     
    1. 基础知识:
    (1) 32位os 指针长度为4字节, 64位os 指针长度为8字节, 下面的分析环境为64位 linux & g++ 4.8.4.
    (2) new一个对象时, 只为类中成员变量分配空间, 对象之间共享成员函数。

    2. _vptr
        运行下上面的代码发现sizeof(Base) = 8, 说明编译器在类中自动添加了一个8字节的成员变量, 这个变量就是_vptr, 指向虚函数表的指针。
    _vptr有些文章里说gcc是把它放在对象内存的末尾,VC是放在开始, 我编译是用的g++,验证了下是放在开始的:

    验证代码:取对象a的地址与a第一个成员变量n的地址比较,如果不等,说明对象地址开始放的是_vptr. 也可以用gdb直接print a 会发现_vptr在开始

    复制代码
    class A
    {
    public:
          int n;
          virtual void Foo(void){}
    };
    
    int main()
    {
         A a;
         char *p1 = reinterpret_cast<char*>(&a);
         char *p2 = reinterpret_cast<char*>(&a.n);
         if(p1 == p2)
         {
            cout<<"vPtr is in the end of class instance!"<<endl;
         }else
         {
            cout<<"vPtr is in the head of class instance!"<<endl;
         }
         return 1;
    }
    复制代码

    (3) 虚函数表
        包含虚函数的类才会有虚函数表, 同属于一个类的对象共享虚函数表, 但是有各自的_vptr.
        虚函数表实质是一个指针数组,里面存的是虚函数的函数指针。

    Base中虚函数表结构:

    Derive中虚函数表结构:

    (4)验证
    运行上面代码结果:
        size of Base: 8
        base::f
        derive::g
        base::h

    说明Derive的虚函数表结构跟上面分析的是一致的:
        d对象的首地址就是vptr指针的地址-pvptr,
        取pvptr的值就是vptr-虚函数表的地址
        取vptr中[0][1][2]的值就是这三个函数的地址
        通过函数地址就直接可以运行三个虚函数了。
        函数表中Base::g()函数指针被Derive中的Derive::g()函数指针覆盖, 所以执行的时候是调用的Derive::g()

    (5)多继承

    复制代码
    附 GDB调试:
    
    (1) #生成带有调试信息的可执行文件
    g++ test.cpp -g -o test    
    
    (2) #载入test
    gdb test
    
    (3) #列出Base类代码
    (gdb) list Base
    1       #include <iostream>
    2
    3       using namespace std;
    4
    5       class Base {
    6       public:
    7           virtual void f() {cout<<"base::f"<<endl;}
    8           virtual void g() {cout<<"base::g"<<endl;}
    9           virtual void h() {cout<<"base::h"<<endl;}
    10      };
    
    4) #查看Base函数地址
    (gdb) info line 7 
    Line 7 of "test.cpp" starts at address 0x400ac8 <Base::f()> and ends at 0x400ad4 <Base::f()+12>.
    (gdb) info line 8
    Line 8 of "test.cpp" starts at address 0x400af2 <Base::g()> and ends at 0x400afe <Base::g()+12>.
    (gdb) info line 9
    Line 9 of "test.cpp" starts at address 0x400b1c <Base::h()> and ends at 0x400b28 <Base::h()+12>.
    
    5)#列出Derive代码
    (gdb) list Derive 
    7           virtual void f() {cout<<"base::f"<<endl;}
    8           virtual void g() {cout<<"base::g"<<endl;}
    9           virtual void h() {cout<<"base::h"<<endl;}
    10      };
    11
    12      class Derive : public Base{
    13      public:
    14          void g() {cout<<"derive::g"<<endl;}
    15      };
    
    6)#查看Derive函数地址
    (gdb) info line 14
    Line 14 of "test.cpp" starts at address 0x400b46 <Derive::g()> and ends at 0x400b52 <Derive::g()+12>.
    
    7)#start执行程序,n单步执行
    (gdb) start
    Temporary breakpoint 1, main () at test.cpp:19
    19          cout<<"size of Base: "<<sizeof(Base)<<endl;
    (gdb) n
    size of Base: 8
    22          Base b;
    (gdb)
    23          Base *d = new Derive();
    (gdb)
    25          long* pvptr = (long*)d;
    (gdb)
    26          long* vptr = (long*)*pvptr;
    (gdb)
    27          Func f = (Func)vptr[0];
    (gdb)
    28          Func g = (Func)vptr[1];
    (gdb)
    29          Func h = (Func)vptr[2];
    (gdb)
    31          f();
    (gdb)
    
    8) #print d对象, 0x400c90为成员变量_vptr的值,也就是函数表的地址
    (gdb) p *d 
    $4 = {_vptr.Base = 0x400c90 <vtable for Derive+16>}
    (gdb) p vptr
    $6 = (long *) 0x400c90 <vtable for Derive+16>
    (9) #查看函数表值,与之前查看函数地址一致
    (gdb) p (long*)vptr[0]
    $9 = (long *) 0x400ac8 <Base::f()>
    (gdb) p (long*)vptr[1]
    $10 = (long *) 0x400b46 <Derive::g()>
    (gdb) p (long*)vptr[2]
    $11 = (long *) 0x400b1c <Base::h()>
    复制代码

    另vptr. vtable内存位置, refer  http://www.tuicool.com/articles/iUB3Ebi

  • 相关阅读:
    优质Android小部件:索尼滚动相册
    Linux常用命令:文件与目录
    Android高效计算——RenderScript(二)
    Android高效计算——RenderScript(一)
    实用控件分享:自定义逼真相机光圈View
    Binder中的asInterface解析
    Android Adapter的几个方法
    Android中各种Drawable总结
    win10配置CUDA+Tensorflow2.0的一些经验
    关于Flash Helper Service的问题
  • 原文地址:https://www.cnblogs.com/senior-engineer/p/7832868.html
Copyright © 2011-2022 走看看