zoukankan      html  css  js  c++  java
  • 类的内存结构

    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 的虚函数,所以可以知道编译器在这里做了处理,避免了菱形继承中尴尬的情况。

  • 相关阅读:
    Linux编程 22 shell编程(输出和输入重定向,管道,数学运算命令,退出脚本状态码)
    mysql 开发进阶篇系列 46 物理备份与恢复( xtrabackup的 选项说明,增加备份用户,完全备份案例)
    mysql 开发进阶篇系列 45 物理备份与恢复(xtrabackup 安装,用户权限,配置)
    mysql 开发进阶篇系列 44 物理备份与恢复( 热备份xtrabackup 工具介绍)
    Linux编程 21 shell编程(环境变量,用户变量,命令替换)
    Linux编程 20 shell编程(shell脚本创建,echo显示信息)
    mysql 开发进阶篇系列 43 逻辑备份与恢复(mysqldump 的基于时间和位置的不完全恢复)
    Linux编程 19 编辑器(vim 用法)
    (网页)angularjs中的interval定时执行功能(转)
    (网页)在SQL Server中为什么不建议使用Not In子查询(转)
  • 原文地址:https://www.cnblogs.com/sinpo828/p/13156042.html
Copyright © 2011-2022 走看看