• 对C++对象内存模型造成的影响(类/对象的大小)


    首先重新回顾一下关于类/对象大小的计算原则:

    类大小计算遵循结构体对齐原则

    第一个数据成员放在offset为0的位置

    其它成员对齐至min(sizeof(member),#pragma pack(n)所指定的值)的整数倍。

    整个结构体也要对齐,结构体总大小对齐至各个min中最大值的整数倍。

    win32 可选的有1, 2, 4, 8, 16
    linux 32 可选的有1, 2, 4
    类的大小与数据成员有关与成员函数无关
    类的大小与静态数据成员无关
    虚继承对类的大小的影响
    虚函数对类的大小的影响

    下面通过实例来展示虚继承和虚函数对类大小造成的影响。

    测试环境为:Win32 + Vs2008

    一、只出现虚继承的情况

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
     
    #include <iostream>
    using namespace std;

    class BB
    {
    public :
          int bb_ ;
    };

    class B1 : virtual public BB
    {
    public :
          int b1_ ;
    };

    class B2 : virtual public BB
    {
    public :
          int b2_ ;
    };

    class DD : public B1, public B2
    {
    public :
          int dd_ ;
    };

    int main (void)
    {
          cout<<sizeof (BB)<< endl;
          cout<<sizeof (B1)<< endl;
          cout<<sizeof (DD)<< endl;

          B1 b1 ;
          int** p ;

          cout<<&b1 <<endl;
          cout<<&b1 .bb_<< endl;
          cout<<&b1 .b1_<< endl;

          p = (int **)&b1;
          cout<<p [0][0]<<endl;
          cout<<p [0][1]<<endl;

          DD dd ;
          cout<<&dd <<endl;
          cout<<&dd .bb_<< endl;
          cout<<&dd .b1_<< endl;
          cout<<&dd .b2_<< endl;
          cout<<&dd .dd_<< endl;
          p = (int **)&dd;
          cout<<p [0][0]<<endl;
          cout<<p [0][1]<<endl;
          cout<<endl ;
          cout<<p [2][0]<<endl;
          cout<<p [2][1]<<endl;

          BB* pp ;

          pp = &dd ;
          dd.bb_ = 10; //对象的内存模型在编译时就已经确定了,否则无法定义类的对象,因为要开辟内存
          int base = pp-> bb_;     // 通过间接访问 (其实pp 已经偏移了20 ),这需要运行时的支持
          cout<<"dd.bb_=" <<base<< endl;

          return 0;
    }



    从输出的地址和虚基类表成员数据可以画出对象内存模型图:

    virtual base table 

    本类地址与虚基类表指针地址的差

    虚基类地址与虚基类表指针地址的差

    virtual base table pointer(vbptr)


    从程序可以看出pp是BB* 指针,通过打印pp 的值与&dd 比较可知,

    cout<<(void*)&dd<<endl;
    cout<<(void*)pp<<endl;

    pp实际上已经偏移了20个字节,如何实现的呢?先找到首个vbptr,找到虚基类BB地址与虚基类表指针地址的差,也即是20,接着pp偏移20个字节指向了dd对象中的BB部分,然后就访问到了bb_,这是在运行时才做的转换。记住:C++标准规定对对象取地址将始终为对应类型的首地址。


    二、只出现虚函数的情况

    (一):一般继承

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
     
    #include <iostream>
    using namespace std;

    class Base
    {
    public :
        virtual void Fun1()
        {
            cout << "Base::Fun1 ..." << endl;
        }

        virtual void Fun2()
        {
            cout << "Base::Fun2 ..." << endl;
        }
        int data1_ ;
    };

    class Derived : public Base
    {
    public :
        void Fun2 ()
        {
            cout << "Derived::Fun2 ..." << endl;
        }
        virtual void Fun3()
        {
            cout << "Derived::Fun3 ..." << endl;
        }
        int data2_ ;
    };

    typedef void (* FUNC)(void );

    int main (void)
    {
        cout << sizeof (Base) << endl;
        cout << sizeof (Derived) << endl;
        Base b ;
        int **p = (int **)& b;
        FUNC fun = (FUNC) p[0][0];
        fun();
        fun = (FUNC )p[0][1];
        fun();
        cout << endl ;

        Derived d ;
        p = (int **)&d;
        fun = (FUNC )p[0][0];
        fun();
        fun = (FUNC )p[0][1];
        fun();
        fun = (FUNC )p[0][2];
        fun();

        return 0;
    }

    从输出的函数体可以画出对象内存模型图:

    vtbl:虚函数表(存放虚函数的函数指针)

    vptr:虚函数表指针


    从输出可以看出,Derived类继承了Base::Fun1,而覆盖了Fun2,此外还有自己的Fun3。注意,因为Fun3是虚函数,才会出现在虚函数表,如果是一般函数是不会的,因为不用通过vptr间接访问。


    (二)、钻石继承

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
     
    #include <iostream>
    using namespace std;

    class BB
    {
    public:
        virtual void vpbb()
        {
            cout << "BB:vpbb().." << endl;
        }
        int bb_;
    };
    class B1 : public BB
    {
    public:
        virtual void vpb1()
        {
            cout << "B1:vpb1().." << endl;
        }
        int b1_;
    };
    class B2 : public BB
    {
    public:
        virtual void vpb2()
        {
            cout << "B2:vpb2().." << endl;
        }
        int b2_;
    };
    class DD : public B1, public B2
    {
    public:
        virtual void vpdd()
        {
            cout << "DD:vpdd().." << endl;
        }
        int dd_;
    };

    typedef void (* FUNC)(void );

    int main()
    {
        cout << sizeof(BB) << endl;
        cout << sizeof(B1) << endl;
        cout << sizeof(DD) << endl;
        cout << endl;

        DD dd ;
        cout << &dd << endl;
        cout << &dd.B1::bb_ << endl;
        cout << &dd.B2::bb_ << endl;
        cout << &dd .b1_ << endl;
        cout << &dd .b2_ << endl;
        cout << &dd .dd_ << endl;
        cout << endl;

        B1 b ;
        int **p = (int **)& b;
        FUNC fun = (FUNC) p[0][0];
        fun();
        fun = (FUNC )p[0][1];
        fun();
        cout << endl ;

        p = (int **)&dd
        fun = (FUNC)p[0][0];
        fun();
        fun = (FUNC)p[0][1];
        fun();
        fun = (FUNC)p[0][2];
        fun();

        fun = (FUNC)p[3][0];
        fun();
        fun = (FUNC)p[3][1];
        fun();

        cout << endl;

        return 0;
    }



    从成员输出的地址和通过虚函数表指针访问到的函数可以画出模型:

    DD::vfdd 的位置跟继承的顺序有关,如果DD先继承的是B2, 那么它将跟在B2::vfb2 的下面。

    如果派生类是从多个基类继承或者有多个继承分支(从所有根类开始算起),而其中若干个继承分支上出现了多态类,则派生类将从这些分支中的每个分支上继承一个vptr,编译器也将为它生成多个vtable,有几个vptr就生成几个vtable(每个vptr分别指向其中一个),分别与它的多态基类对应。


    三、虚继承与虚函数同时出现的情况:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
     
    #include <iostream>
    using namespace std;

    class BB
    {
    public :
          virtual void vfbb()
         {
               cout<<"BB::vfbb" <<endl;
         }
          virtual void vfbb2()
         {
               cout<<"BB::vfbb2" <<endl;
         }
          int bb_ ;
    };

    class B1 : virtual public BB
    {
    public :
          virtual void vfb1()
         {
               cout<<"B1::vfb1" <<endl;
         }
          int b1_ ;
    };

    class B2 : virtual public BB
    {
    public :
          virtual void vfb2()
         {
               cout<<"B2::vfb2" <<endl;
         }
          int b2_ ;
    };

    class DD : public B1, public B2
    {
    public :
          virtual void vfdd()
         {
               cout<<"DD::vfdd" <<endl;
         }
          int dd_ ;
    };

    typedef void (* FUNC)(void);

    int main (void)
    {
          cout<<sizeof (BB)<< endl;
          cout<<sizeof (B1)<< endl;
          cout<<sizeof (DD)<< endl;

          BB bb ;
          int** p ;
          p = (int **)&bb;
          FUNC fun ;
          fun = (FUNC )p[0][0];
          fun();
          fun = (FUNC )p[0][1];
          fun();
          cout<<endl ;


          B1 b1 ;
         
          p = (int **)&b1;
          fun = (FUNC )p[0][0];
          fun();
          fun = (FUNC )p[3][0];
          fun();
          fun = (FUNC )p[3][1];
          fun();

          cout<<p [1][0]<<endl;
          cout<<p [1][1]<<endl;
          cout<<endl ;



          DD dd ;
          p = (int **)&dd;
          fun = (FUNC )p[0][0];
          fun();
          fun = (FUNC )p[0][1]; // DD::vfdd 挂在 B1::vfb1的下面
          fun();
          fun = (FUNC )p[3][0];
          fun();
          fun = (FUNC )p[7][0];
          fun();
          fun = (FUNC )p[7][1];
          fun();
         
          cout<<p [1][0]<<endl;
          cout<<p [1][1]<<endl;
          cout<<p [4][0]<<endl;
          cout<<p [4][1]<<endl;


          return 0;
    }



    从输出的虚基类表成员数据和虚函数体可以画出对象内存模型图:


    上图中vfdd 出现的位置跟继承的顺序有关,如果DD先继承的是B2,那么它将跟在vfb2 的下面。

    注意:如果没有虚继承,则虚函数表会合并,一个类只会存在一个虚函数表和一个虚函数表指针(同个类的对象共享),当然也不会有虚基类表和虚基类表指针的存在。
    但如果是钻石继承,那么是会存在两份虚函数表和两份虚函数表指针的。


    参考:

    《深入探索C++对象模型》

    C++ primer 第四版
    Effective C++ 3rd
    C++编程规范

  • 相关阅读:
    vue开发chrome扩展,数据通过storage对象获取
    Vue手动集成less预编译器
    Google Translate寻找之旅
    Javascript Range对象的学习
    Javascript Promises学习
    SublimeText 建立构建Node js系统
    We're sorry but demo3 doesn't work properly without JavaScript enabled. Please enable it to continue.
    npm安装包出现UNMET DEPENDENCY报错
    (转载)命令行说明中格式 尖括号 中括号的含义
    Linux重启网卡服务Failed to start LSB: Bring up/down networking.
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8471110.html
Copyright © 2020-2023  润新知