• 类的内存结构


    cpp 类的内存结构


    说明:

    1. 虚表指针总是存在在类的头部,并按类的继承顺序排放。一个子类可以有多个虚表指针,且虚指针个数和具有虚函数的基类个数相同。

    2. 虚成员函数总是按照声明顺序存在于虚表中。

    3. 如果存在同名函数,子类虚函数会覆盖每一个父类的每一个同名虚函数。

    4. 子类独有的虚函数填入第一个虚函数表中,且用父类指针是不能调用。

    5. 父类独有的虚函数不会被覆盖覆盖。仅子类和该父类指针能调用。

    如下图类的内存结构图

    图中代码参考链接

    无虚函数

    class Drive
    {
    public:
        // virtual void vf() {}
        void f() {}
    };
    
    int main()
    {
        Drive d;
    
        cout << sizeof(d) << endl;
        return 0;
    }
    

    如下,类中没有虚函数,只有一个成员函数,以及其他默认构造析构函数。类似于空类,类的大小为1(注意空类大小不为0,因为为0的话,实例化后没法区分)。因此可以的出结论类的非虚成员函数信息不存在于对象实例中!

    Reading symbols from ./a.out...
    (gdb) b 17
    Breakpoint 1 at 0x1197: file demo.cpp, line 17.
    (gdb) r
    Starting program: /home/lester/Wdir/test/a.out 
    1
    
    Breakpoint 1, main () at demo.cpp:17
    17          return 0;
    (gdb) p d
    $1 = {<No data fields>}
    (gdb) $1 = {<No data fields>}
    (gdb) q
    A debugging session is active.
    
            Inferior 1 [process 54798] will be killed.
    
    Quit anyway? (y or n) y
    ~/Wdir/test >>> ./a.out                                                                                                                                                                      
    1
    ~/Wdir/test >>>      
    

    无继承

    代码:

    class Drive
    {
    public:
        virtual void vf() {}
        void f() {}
    };
    
    int main()
    {
        Drive d;
    
        return 0;
    }
    

    如下,子类经过强制类型转换,得到虚表指针,并提取虚表指针的内容,经过转换可以得到第一个虚函数。虚表中只有一个虚函数。

    (gdb) b 69
    Breakpoint 1 at 0x13c1: file xx.cpp, line 69.
    (gdb) r
    Starting program: /home/lester/tools/xxx/a.out 
    
    Breakpoint 1, main () at xx.cpp:69
    69	    return 0;
    (gdb) p d
    $1 = {_vptr.Drive = 0x555555557da0 <vtable for Drive+16>}
    (gdb) p (int64_t*)**(int64_t**)&d
    $2 = (int64_t *) 0x55555555543a <Drive::vf()>
    (gdb) 
    
    

    单继承

    class Base1
    {
    public:
        virtual void vb1f() {}
        virtual void vf() {}
    };
    
    class Drive : public Base1
    {
    public:
        virtual void vdf() {}
        virtual void vf() {}
        void f() {}
    };
    
    int main()
    {
        Drive d;
    
        return 0;
    }
    

    虚表中只有多个虚函数。顺序是父类,子类的顺序。其中注意到双方共有的虚函数 “vf”, 在虚表中子类的虚函数覆盖了父类的需函数。

    (gdb) b 70
    Breakpoint 1 at 0x13c1: file xx.cpp, line 70.
    (gdb) r
    Starting program: /home/lester/tools/xxx/a.out 
    
    Breakpoint 1, main () at xx.cpp:70
    70	    return 0;
    (gdb) p (int64_t*)*(*((int64_t**)&d)+0)
    $1 = (int64_t *) 0x55555555543a <Base1::vb1f()>
    (gdb) p (int64_t*)*(*((int64_t**)&d)+1)
    $2 = (int64_t *) 0x555555555452 <Drive::vf()>
    (gdb) p (int64_t*)*(*((int64_t**)&d)+2)
    $3 = (int64_t *) 0x555555555446 <Drive::vdf()>
    (gdb) 
    

    多继承

    class Base1
    {
    public:
        virtual void vb1f() {}
        virtual void vf() {}
    };
    
    class Base2
    {
    public:
        virtual void vb2f() {}
        virtual void vf() {}
    };
    
    class Drive : public Base1, Base2
    {
    public:
        virtual void vdf() {}
        virtual void vf() {}
        void f() {}
    };
    
    int main()
    {
        Drive d;
    
        return 0;
    }
    

    虚表中只有多个虚函数。顺序是父类Base1, 父类Base2,子类。

    查看第一个虚表:其中注意到双方共有的虚函数 “vf”, 在虚表中子类的虚函数覆盖了父类的需函数。另外子类的虚函数 ”vdf“ 被放在了第一个虚表的后面。

    查看第二个虚表:第二个虚表指针在地一个虚表指针后面。同样方式可以看到第二个虚表只有父类 Base2 的虚成员函数,而且共有的虚函数被子类的虚函数 vf 覆盖。

    (gdb) b 71
    Breakpoint 1 at 0x13cc: file xx.cpp, line 71.
    (gdb) r
    Starting program: /home/lester/tools/xxx/a.out 
    
    Breakpoint 1, main () at xx.cpp:71
    71	    return 0;
    (gdb) 
    (gdb) p d
    $10 = {<Base1> = {_vptr.Base1 = 0x555555557d28 <vtable for Drive+16>}, <Base2> = {_vptr.Base2 = 0x555555557d50 <vtable for Drive+56>}, <No data fields>}
    (gdb) p (int64_t*)*(*((int64_t**)&d)+0)
    $1 = (int64_t *) 0x555555555446 <Base1::vb1f()>
    (gdb) p (int64_t*)*(*((int64_t**)&d)+1)
    $2 = (int64_t *) 0x55555555546a <Drive::vf()>
    (gdb) p (int64_t*)*(*((int64_t**)&d)+2)
    $3 = (int64_t *) 0x55555555545e <Drive::vdf()>
    
    
    (gdb) p d
    $10 = {<Base1> = {_vptr.Base1 = 0x555555557d28 <vtable for Drive+16>}, <Base2> = {_vptr.Base2 = 0x555555557d50 <vtable for Drive+56>}, <No data fields>}
    (gdb) p (int64_t*)*((int64_t*)*(int64_t*)(*((int64_t**)&d)+1)+0)
    $12 = (int64_t *) 0x555555555452 <Base2::vb2f()>
    (gdb) p (int64_t*)*((int64_t*)*(int64_t*)(*((int64_t**)&d)+1)+1)
    $13 = (int64_t *) 0x555555555475 <non-virtual thunk to Drive::vf()>
    (gdb) 
    

    虚继承(菱形继承)

    单虚继承情况和单继承完全一样,这里忽略,直接描述虚继承的菱形继承情况

    class Base
    {
    public:
       virtual void vbbf() {}
       virtual void vbf() {}
    };
    
    class Base1 : virtual public Base
    {
    public:
       virtual void vb1f() {}
       virtual void vf() {}
    };
    
    class Base2 : virtual public Base
    {
    public:
       virtual void vb2f() {}
       virtual void vf() {}
    };
    
    class Drive : virtual public Base1, virtual public Base2
    {
    public:
       virtual void vdf() {}
       virtual void vf() {}
       void f() {}
    };
    
    int main()
    {
       Base1 b1;
       Base2 b2;
       Drive d;
    
       return 0;
    }
    

    虚多继承

    Breakpoint 1 at 0x1194: file xx.cpp, line 41.
    (gdb) r
    Starting program: /home/lester/Wdir/src/myself/test/a.out 
    
    Breakpoint 1, main () at xx.cpp:41
    41         return 0;
    (gdb) p (int64_t*)*((int64_t*)d+0)
    $1 = (int64_t *) 0x55555555520e <Base::vbbf()>
    (gdb) p (int64_t*)*((int64_t*)d+1)
    $2 = (int64_t *) 0x55555555521a <Base::vbf()>
    (gdb) p (int64_t*)*((int64_t*)d+2)
    $3 = (int64_t *) 0x555555555226 <Base1::vb1f()>
    (gdb) p (int64_t*)*((int64_t*)d+3)
    $4 = (int64_t *) 0x555555555262 <Drive::vf()>
    (gdb) p (int64_t*)*((int64_t*)d+4)
    $5 = (int64_t *) 0x555555555256 <Drive::vdf()>
    (gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+0)
    $7 = (int64_t *) 0x0
    (gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+1)
    $11 = (int64_t *) 0x0
    (gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+2)
    $8 = (int64_t *) 0x55555555523e <Base2::vb2f()>
    (gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+3)
    $9 = (int64_t *) 0x55555555526d <virtual thunk to Drive::vf()>
    (gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+4)
    $10 = (int64_t *) 0x555555557ad8 <vtable for Drive+72>
    (gdb) 
    

    如上可以看到地一个虚表和预期完全一样,按照继承的顺序,虚函数的顺序存在虚表中。
    但是第二个虚表就不一样了,第二个虚表的前两个成员都是空的,并不是指向 Base 的虚函数,所以可以知道编译器在这里做了处理,避免了菱形继承中尴尬的情况。

  • 相关阅读:
    php错误:Uncaught exception com_exception with message Failed to create COM object
    PHP调用OCX控件的具体方法
    ESA2GJK1DH1K升级篇: STM32远程乒乓升级,升级流程源码详细说明
    ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于(GPRS模块AT指令TCP透传方式),定时访问升级(兼容Air202,SIM800)
    数据库: 安装配置数据库,使用Navicat for MySQL和手机APP 连接测试(如果上一节碰到问题可参考这一节)
    数据库: 安装配置数据库,使用Navicat for MySQL和手机APP 连接测试
    数据库: 简要安装使用
    OpenResty: 反向代理
    OpenResty: PHP增加数据库插件
    ESA2GJK1DH1K微信小程序篇: 安装Nginx,配置反向代理
  • 原文地址:https://www.cnblogs.com/sinpo828/p/13156042.html
Copyright © 2020-2023  润新知