C++菱形继承内存布局分析


一、编译环境

  • Visual Studio 2017
  • G++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
  • Clang++ 6.0.0-1ubuntu2

二、非虚继承

2.1 测试代码

// filename: nonvirtual.cpp
#include <cstdio>

struct A
{
    virtual void FA() {}
    long long a = 0x1111111111111111LL;
};

struct B : A
{
    virtual void FB() {}
    long long b = 0x2222222222222222LL;
};

struct C : A
{
    virtual void FC() {}
    long long c = 0x3333333333333333LL;
};

struct D : B, C
{
    virtual void FD() {}
    long long d = 0x4444444444444444LL;
};

int main(int argc, char** argv)
{
    A a;
    B b;
    C c;
    D d;

    printf("sizeof(void*) = %zu\n", sizeof(void*));
    printf("sizeof(a) = %zu, &a = %p\n", sizeof(a), &a);
    printf("sizeof(b) = %zu, &b = %p\n", sizeof(b), &b);
    printf("sizeof(c) = %zu, &c = %p\n", sizeof(c), &c);
    printf("sizeof(d) = %zu, &d = %p\n", sizeof(d), &d);

    return 0;
}

2.2 编译方式

2.2.1 VS20177

Debug x64

2.2.2 G++

g++ -std=c++11 -ggdb -o gcc-nv.out nonvirtual.cpp

2.2.3 Clang++

clang++ -std=c++11 -ggdb -o clang-nv.out nonvirtual.cpp

2.3 执行结果

2.3.1 VS2017

  1. 执行结果

    sizeof(void*) = 8
    sizeof(a) = 16, &a = 000000204193F818
    sizeof(b) = 24, &b = 000000204193F848
    sizeof(c) = 24, &c = 000000204193F878
    sizeof(d) = 56, &d = 000000204193F8A8
  2. 类D的内存布局

    +----------------+
    |                |
    | +------------+ |
    | |            | |      +---------+
    | | +--------+ | |      |         |
    | | | A.vptr+---------->+ A::FA() |
    | | | A::a   | | |      | B::FB() |
    | | +--------+ | |      | D::FD() |
    | |   B::b     | |      |         |
    | +------------+ |      +---------+
    |                |
    | +------------+ |
    | |            | |      +---------+
    | | +--------+ | |      |         |
    | | | A.vptr+---------->+ A::FA() |
    | | | A::a   | | |      | C::FC() |
    | | +--------+ | |      |         |
    | |   C::c     | |      +---------+
    | +------------+ |
    |                |
    |     D::d       |
    |                |
    +----------------+
  3. 类D的内存DUMP

    0x000000204193F8A8  00007ff7c032b1e8 1111111111111111 2222222222222222 00007ff7c032ae18 1111111111111111 3333333333333333
    0x000000204193F8D8  4444444444444444
    
    0x00007FF7C032B1E8  00007ff7c0321302 00007ff7c03211b8 00007ff7c032105a
    
    0x00007FF7C032AE18  00007ff7c0321302 00007ff7c03212e4

2.3.2 G++

  1. 执行结果

    sizeof(void*) = 8
    sizeof(a) = 16, &a = 0x7fffffffe350
    sizeof(b) = 24, &b = 0x7fffffffe360
    sizeof(c) = 24, &c = 0x7fffffffe380
    sizeof(d) = 56, &d = 0x7fffffffe3a0
  2. 类D的内存布局

    和VS2017的结果类似。

  3. 类D的内存DUMP

    (gdb) x /7a &d
    0x7fffffffe3a0: 0x555555755ca8 <_ZTV1D+16>      0x1111111111111111
    0x7fffffffe3b0: 0x2222222222222222      0x555555755cd0 <_ZTV1D+56>
    0x7fffffffe3c0: 0x1111111111111111      0x3333333333333333
    0x7fffffffe3d0: 0x4444444444444444
    (gdb) x /3a 0x555555755ca8
    0x555555755ca8 <_ZTV1D+16>:     0x555555554b96 <A::FA()>        0x555555554ba2 <B::FB()>
    0x555555755cb8 <_ZTV1D+32>:     0x555555554bba <D::FD()>
    (gdb) x /2a 0x555555755cd0
    0x555555755cd0 <_ZTV1D+56>:     0x555555554b96 <A::FA()>        0x555555554bae <C::FC()>

2.3.3 Clang++

  1. 执行结果

    sizeof(void*) = 8
    sizeof(a) = 16, &a = 0x7fffffffe3c0
    sizeof(b) = 24, &b = 0x7fffffffe3a8
    sizeof(c) = 24, &c = 0x7fffffffe390
    sizeof(d) = 56, &d = 0x7fffffffe358
  2. 类D的内存布局

    和VS2017的结果类似。

  3. 类D的内存DUMP

    (gdb) x /7a &d
    0x7fffffffe358: 0x400a88 <_ZTV1D+16>    0x1111111111111111
    0x7fffffffe368: 0x2222222222222222      0x400ab0 <_ZTV1D+56>
    0x7fffffffe378: 0x1111111111111111      0x3333333333333333
    0x7fffffffe388: 0x4444444444444444
    (gdb) x /3a 0x400a88
    0x400a88 <_ZTV1D+16>:   0x400880 <A::FA()>      0x400890 <B::FB()>
    0x400a98 <_ZTV1D+32>:   0x4008b0 <D::FD()>
    (gdb) x /2a 0x400ab0
    0x400ab0 <_ZTV1D+56>:   0x400880 <A::FA()>      0x4008a0 <C::FC()>

三、虚继承

3.1 测试代码

非虚拟继承的测试代码类似,只是将非虚拟继承改成了虚拟继承。

// filename: virtual.cpp
#include <cstdio>

struct A
{
    virtual void FA() {}
    long long a = 0x1111111111111111LL;
};

struct B : virtual public A
{
    virtual void FB() {}
    long long b = 0x2222222222222222LL;
};

struct C : virtual public A
{
    virtual void FC() {}
    long long c = 0x3333333333333333LL;
};

struct D : B, C
{
    virtual void FD() {}
    long long d = 0x4444444444444444LL;
};

int main(int argc, char** argv)
{
    A a;
    B b;
    C c;
    D d;

    auto pba = dynamic_cast<A*>(&b);
    auto pda = dynamic_cast<A*>(&d);
    auto pdb = dynamic_cast<B*>(&d);

    printf("sizeof(void*) = %zu\n", sizeof(void*));
    printf("sizeof(a) = %zu, &a = %p\n", sizeof(a), &a);
    printf("sizeof(b) = %zu, &b = %p\n", sizeof(b), &b);
    printf("sizeof(c) = %zu, &c = %p\n", sizeof(c), &c);
    printf("sizeof(d) = %zu, &d = %p\n", sizeof(d), &d);

    return 0;
}

3.2 编译方式

3.2.1 vs2017

Debug x64

3.2.2 G++

g++ -std=c++11 -ggdb -o gcc-v.out virtual.cpp

3.2.3 Clang++

clang++ -std=c++11 -ggdb -o clang-v.out virtual.cpp

3.3 执行结果

VS2017的执行结果与G++和Clang++不同。

3.3.1 VS2017

  1. 执行结果

    sizeof(void*) = 8
    sizeof(a) = 16, &a = 00000002F65FF658
    sizeof(b) = 40, &b = 00000002F65FF688
    sizeof(c) = 40, &c = 00000002F65FF6C8
    sizeof(d) = 72, &d = 00000002F65FF710
  2. 对类B的分析

    • 类B的内存分布

                      +--------------+
                      |              |
      +---------+     | +----------+ |
      | B::FB() +<--------+B.vptr   | |      +--------------------+
      +---------+     | | B.offset+-------->+ offset2ptr    = -8 |
                      | | B::b     | |      | offset2A.vptr = 16 |
                      | +----------+ |      +--------------------+
                      |              |
      +---------+     | +----------+ |
      | A::FA() +<-------+A.vptr   | |
      +---------+     | | A::a     | |
                      | +----------+ |
                      |              |
                      +--------------+
    • 类B的内存DUMP

      0x00000002F65FF688  00007ff6cd4eadb0 00007ff6cd4eb1e0 2222222222222222 00007ff6cd4eadc8 1111111111111111
      
      0x00007FF6CD4EADB0  00007ff6cd4e11b8 <B::FB()>
      
      0x00007FF6CD4EB1E0  fffffff8 00000010
      
      0x00007FF6CD4EADC8  00007ff6cd4e1302 <A::FA()>
  3. 对类D的分析

    • 类D的内存布局

      offset中,offset2ptr的数据是没使用的。

                      +--------------+
                      |              |
      +---------+     | +----------+ |
      | B::FB() +<-------+B.vptr   | |      +--------------------+
      | D::FD() |     | | B.offset+-------->+ offset2ptr    = -8 |
      +---------+     | | B::b     | |      | offset2A.vptr = 48 |
                      | +----------+ |      | offset2vptr   = -8 |
                      |              |      | offset2C.vptr = 24 |
      +---------+     | +----------+ |      +--------------------+
      | C::FC() +<-------+C.vptr   | |      +--------------------+
      +---------+     | | C.offset+-------->+ offset2vptr   = -8 |
                      | | C::c     | |      | offset2A.vptr = 24 |
                      | +----------+ |      +--------------------+
                      |              |
                      |   D::d       |
                      |              |
      +---------+     | +----------+ |
      | A::FA() +<-------+A.vptr   | |
      +---------+     | | A::a     | |
                      | +----------+ |
                      |              |
                      +--------------+
    • 类D的内存DUMP

      0x00000002F65FF710  00007ff6cd4eb098 00007ff6cd4eac10 2222222222222222 00007ff6cd4eae70 00007ff6cd4eac18 3333333333333333
      0x00000002F65FF740  4444444444444444 00007ff6cd4eae90 1111111111111111
      
      0x00007FF6CD4EB098  00007ff6cd4e11b8 <B::FB()> 00007ff6cd4e105a <D::FD()>
      
      0x00007FF6CD4EAC10  fffffff8 00000030 fffffff8 00000018
      
      0x00007FF6CD4EAE70  00007ff6cd4e12e4 <C::FC()>
      
      0x00007FF6CD4EAC18  fffffff8 00000018
      
      0x00007FF6CD4EAE90  00007ff6cd4e1302 <A::FA()>

3.3.2 G++

  1. 执行结果

    sizeof(void*) = 8
    sizeof(a) = 16, &a = 0x7fffffffe360
    sizeof(b) = 32, &b = 0x7fffffffe370
    sizeof(c) = 32, &c = 0x7fffffffe390
    sizeof(d) = 56, &d = 0x7fffffffe3b0
  2. 对类B的分析

    • 类B的内存DUMP

    • 类B的内存布局

      +------------+
      |            |
      | +--------+ |     +---------+
      | | B.vptr+------->+ B::FB() |
      | | B::b   | |     +---------+
      | +--------+ |
      | +--------| |     +---------+
      | | A.vptr+------->+ A::FA() |
      | | A::a   | |     +---------+
      | +--------+ |
      |            |
      +------------+
      >>> x /4a &b
      0x7fffffffe370: 0x555555755cc8 <_ZTV1B+24>      0x2222222222222222
      0x7fffffffe380: 0x555555755ce8 <_ZTV1B+56>      0x1111111111111111
      >>> x /1a 0x555555755cc8
      0x555555755cc8 <_ZTV1B+24>:     0x555555554dc4 <B::FB()>
      >>> x /1a 0x555555755ce8
      0x555555755ce8 <_ZTV1B+56>:     0x555555554db8 <A::FA()>
  3. 对类D的分析

    • 类D的内存布局

      +----------------+
      |                |
      | +------------+ |
      | |            | |
      | | +--------+ | |      +---------+
      | | | B.vptr+---------->+ B::FB() |
      | | | B::b   | | |      | D::FD() |
      | | +--------+ | |      +---------+
      | | +--------| | |      +---------+
      | | | C.vptr+---------->+ C::FC() |
      | | | C::c   | | |      +---------+
      | | +--------+ | |
      | |            | |
      | +------------+ |
      |                |
      |     D::d       |
      |                |
      |   +--------+   |      +---------+
      |   | A.vptr+---------->+ A::FA() |
      |   | A::a   |   |      +---------+
      |   +--------+   |
      |                |
      +----------------+
    • 类D的内存DUMP

      >>> x /7a &d
      0x7fffffffe3b0: 0x555555755b58 <_ZTV1D+24>      0x2222222222222222
      0x7fffffffe3c0: 0x555555755b80 <_ZTV1D+64>      0x3333333333333333
      0x7fffffffe3d0: 0x4444444444444444      0x555555755ba0 <_ZTV1D+96>
      0x7fffffffe3e0: 0x1111111111111111
      >>> x /2a 0x555555755b58
      0x555555755b58 <_ZTV1D+24>:     0x555555554dc4 <B::FB()>        0x555555554ddc <D::FD()>
      >>> x /1a 0x555555755b80
      0x555555755b80 <_ZTV1D+64>:     0x555555554dd0 <C::FC()>
      >>> x /1a 0x555555755ba0
      0x555555755ba0 <_ZTV1D+96>:     0x555555554db8 <A::FA()>

3.3.3 Clang++

Clang++的结果和G++的一样。

  1. 执行结果

    sizeof(void*) = 8
    sizeof(a) = 16, &a = 0x7fffffffe3c0
    sizeof(b) = 32, &b = 0x7fffffffe3a0
    sizeof(c) = 32, &c = 0x7fffffffe380
    sizeof(d) = 56, &d = 0x7fffffffe348
  2. 对类B的分析

    • 类B的内存布局

      +------------+
      |            |
      | +--------+ |     +---------+
      | | B.vptr+------->+ B::FB() |
      | | B::b   | |     +---------+
      | +--------+ |
      | +--------| |     +---------+
      | | A.vptr+------->+ A::FA() |
      | | A::a   | |     +---------+
      | +--------+ |
      |            |
      +------------+
    • 类B的内存DUMP

      >>> x /4a &b
      0x7fffffffe3a0: 0x400b60 <_ZTV1B+24>    0x2222222222222222
      0x7fffffffe3b0: 0x400b80 <_ZTV1B+56>    0x1111111111111111
      >>> x /1a 0x400b60
      0x400b60 <_ZTV1B+24>:   0x400960 <B::FB()>
      >>> x /1a 0x400b80
      0x400b80 <_ZTV1B+56>:   0x400950 <A::FA()>
  3. 对类D的分析

    • 类D的内存布局

      +----------------+
      |                |
      | +------------+ |
      | |            | |
      | | +--------+ | |      +---------+
      | | | B.vptr+---------->+ B::FB() |
      | | | B::b   | | |      | D::FD() |
      | | +--------+ | |      +---------+
      | | +--------| | |      +---------+
      | | | C.vptr+---------->+ C::FC() |
      | | | C::c   | | |      +---------+
      | | +--------+ | |
      | |            | |
      | +------------+ |
      |                |
      |     D::d       |
      |                |
      |   +--------+   |      +---------+
      |   | A.vptr+---------->+ A::FA() |
      |   | A::a   |   |      +---------+
      |   +--------+   |
      |                |
      +----------------+
    • 类D的内存DUMP

      >>> x /7a &d
      0x7fffffffe348: 0x400c60 <_ZTV1D+24>    0x2222222222222222
      0x7fffffffe358: 0x400c88 <_ZTV1D+64>    0x3333333333333333
      0x7fffffffe368: 0x4444444444444444      0x400ca8 <_ZTV1D+96>
      0x7fffffffe378: 0x1111111111111111
      >>> x /2a 0x400c60
      0x400c60 <_ZTV1D+24>:   0x400960 <B::FB()>      0x400a00 <D::FD()>
      >>> x /1a 0x400c88
      0x400c88 <_ZTV1D+64>:   0x400970 <C::FC()>
      >>> x /1a 0x400ca8
      0x400ca8 <_ZTV1D+96>:   0x400950 <A::FA()>

四、分析结论

4.1 结论1

菱形非虚继承时 VS2017 、 G++ 7.5 和 Clang++ 6 编译生成的类的内存布局相同。

+----------------+
|                |
| +------------+ |
| |            | |      +---------+
| | +--------+ | |      |         |
| | | A.vptr+---------->+ A::FA() |
| | | A::a   | | |      | B::FB() |
| | +--------+ | |      | D::FD() |
| |   B::b     | |      |         |
| +------------+ |      +---------+
|                |
| +------------+ |
| |            | |      +---------+
| | +--------+ | |      |         |
| | | A.vptr+---------->+ A::FA() |
| | | A::a   | | |      | C::FC() |
| | +--------+ | |      |         |
| |   C::c     | |      +---------+
| +------------+ |
|                |
|     D::d       |
|                |
+----------------+

4.2 结论2

菱形虚拟继承时 VS2017 编译生成类的内存布局与 G++ 7.5 和 Clang++ 6 的结果不同。

  • VS2017生成的类的内存布局:

                    +--------------+
                    |              |
    +---------+     | +----------+ |
    | B::FB() +<--------+B.vptr   | |      +--------------------+
    | D::FD() |     | | B.offset+-------->+ offset2ptr    = -8 |
    +---------+     | | B::b     | |      | offset2A.vptr = 48 |
                    | +----------+ |      | offset2vptr   = -8 |
                    |              |      | offset2C.vptr = 24 |
    +---------+     | +----------+ |      +--------------------+
    | C::FC() +<-------+C.vptr   | |      +--------------------+
    +---------+     | | C.offset+-------->+ offset2vptr   = -8 |
                    | | C::c     | |      | offset2A.vptr = 24 |
                    | +----------+ |      +--------------------+
                    |              |
                    |   D::d       |
                    |              |
    +---------+     | +----------+ |
    | A::FA() +<-------+A.vptr   | |
    +---------+     | | A::a     | |
                    | +----------+ |
                    |              |
                    +--------------+
  • G++ 7.5 和 Clang++ 6 生成的类的内存布局相同:

    +----------------+
    |                |
    | +------------+ |
    | |            | |
    | | +--------+ | |      +---------+
    | | | B.vptr+---------->+ B::FB() |
    | | | B::b   | | |      | D::FD() |
    | | +--------+ | |      +---------+
    | | +--------| | |      +---------+
    | | | C.vptr+---------->+ C::FC() |
    | | | C::c   | | |      +---------+
    | | +--------+ | |
    | |            | |
    | +------------+ |
    |                |
    |     D::d       |
    |                |
    |   +--------+   |      +---------+
    |   | A.vptr+---------->+ A::FA() |
    |   | A::a   |   |      +---------+
    |   +--------+   |
    |                |
    +----------------+

文章作者: Kiba Amor
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 Kiba Amor !
  目录