c++内存对象模型--vs2017下的分析,32位

1、没有虚继承,没有虚函数的情况:

class VB
{
public:
    int m_a;
    //virtual void print() {
    //    cout << "VB" << endl;
    //}
};
class Base : public VB
{
public:
    int m_b1;
    //virtual void pb()
    //{
    //    cout << "pb" << endl;
    //}
};

class Base2 : public VB
{
public:
    int m_b2;
    //virtual void pb2()
    //{
    //    cout << "pb2" << endl;
    //}
};

class Derive : public Base, public Base2
{
public:
    int m_d;
    //virtual void pb()
    //{
    //    cout << "Derive pb" << endl;
    //}
    //virtual void pb2()
    //{
    //    cout << "Derive pb2" << endl;
    //}
};
View Code

vs里得到的内存对象:

 1 1>class VB    size(4):
 2 1>    +---
 3 1> 0    | m_a
 4 1>    +---
 5 1>
 6 1>class Base    size(8):
 7 1>    +---
 8 1> 0    | +--- (base class VB)
 9 1> 0    | | m_a
10 1>    | +---
11 1> 4    | m_b1
12 1>    +---
13 1>
14 1>class Base2    size(8):
15 1>    +---
16 1> 0    | +--- (base class VB)
17 1> 0    | | m_a
18 1>    | +---
19 1> 4    | m_b2
20 1>    +---
21 1>
22 1>class Derive    size(20):
23 1>    +---
24 1> 0    | +--- (base class Base)
25 1> 0    | | +--- (base class VB)
26 1> 0    | | | m_a
27 1>    | | +---
28 1> 4    | | m_b1
29 1>    | +---
30 1> 8    | +--- (base class Base2)
31 1> 8    | | +--- (base class VB)
32 1> 8    | | | m_a
33 1>    | | +---
34 1>12    | | m_b2
35 1>    | +---
36 1>16    | m_d
37 1>    +---
38 1>

从上面信息可以得知:

  1)、继承在对象内的布局是按继承的从左到右顺序,依次放到派生类对象内的;

  2)、可以看到VB在Derive里面有两份(虚继承就是为了解决这个问题);

  3)派生类对象的指针转换为基类指针,只需要进行偏移即可。

2、有虚函数的情况:

class VB
{
public:
    int m_a;
    virtual void print() {
        cout << "VB" << endl;
    }
};
class Base : public VB
{
public:
    int m_b1;
    virtual void pb()
    {
        cout << "pb" << endl;
    }
};

class Base2 : public VB
{
public:
    int m_b2;
    virtual void pb2()
    {
        cout << "pb2" << endl;
    }
};

class Derive : public Base, public Base2
{
public:
    int m_d;
    virtual void pb()
    {
        cout << "Derive pb" << endl;
    }
    virtual void pb2()
    {
        cout << "Derive pb2" << endl;
    }
};
View Code

vs里得到的内存对象:

 1 1>class VB    size(8):
 2 1>    +---
 3 1> 0    | {vfptr}
 4 1> 4    | m_a
 5 1>    +---
 6 1>
 7 1>VB::$vftable@:
 8 1>    | &VB_meta
 9 1>    |  0
10 1> 0    | &VB::print
11 1>
12 1>VB::print this adjustor: 0
13 1>
14 1>class Base    size(12):
15 1>    +---
16 1> 0    | +--- (base class VB)
17 1> 0    | | {vfptr}
18 1> 4    | | m_a
19 1>    | +---
20 1> 8    | m_b1
21 1>    +---
22 1>
23 1>Base::$vftable@:
24 1>    | &Base_meta
25 1>    |  0
26 1> 0    | &VB::print
27 1> 1    | &Base::pb
28 1>
29 1>Base::pb this adjustor: 0
30 1>
31 1>class Base2    size(12):
32 1>    +---
33 1> 0    | +--- (base class VB)
34 1> 0    | | {vfptr}
35 1> 4    | | m_a
36 1>    | +---
37 1> 8    | m_b2
38 1>    +---
39 1>
40 1>Base2::$vftable@:
41 1>    | &Base2_meta
42 1>    |  0
43 1> 0    | &VB::print
44 1> 1    | &Base2::pb2
45 1>
46 1>Base2::pb2 this adjustor: 0
47 1>
48 1>class Derive    size(28):
49 1>    +---
50 1> 0    | +--- (base class Base)
51 1> 0    | | +--- (base class VB)
52 1> 0    | | | {vfptr}
53 1> 4    | | | m_a
54 1>    | | +---
55 1> 8    | | m_b1
56 1>    | +---
57 1>12    | +--- (base class Base2)
58 1>12    | | +--- (base class VB)
59 1>12    | | | {vfptr}
60 1>16    | | | m_a
61 1>    | | +---
62 1>20    | | m_b2
63 1>    | +---
64 1>24    | m_d
65 1>    +---
66 1>
67 1>Derive::$vftable@Base@:
68 1>    | &Derive_meta
69 1>    |  0
70 1> 0    | &VB::print
71 1> 1    | &Derive::pb
72 1>
73 1>Derive::$vftable@Base2@:
74 1>    | -12
75 1> 0    | &VB::print
76 1> 1    | &Derive::pb2
77 1>
78 1>Derive::pb this adjustor: 0
79 1>Derive::pb2 this adjustor: 12
80 1>

从上面信息可以得知:

  1)VB的大小为8,其中一个虚函数指针,一个int成员变量;

  2)Derive的大小为28,其中一个Base(12Byte),一个Base(12Byte),一个自己的成员变量int m_d,一共28Byte;

  3)我们知道,对象的虚函数指针指向虚表,虚表里存着函数指针,那么具体是怎么实现的多态呢?

    从Base来看:继承了VB,虚函数指针在对象的第0个地址;

    从Derive来看:由于继承顺序,所以Base类放在的第一个位置,对应的虚函数指针也按Base里的顺序放置的;

    再根据表Derive::$vftable@Base@ 和 Derive::$vftable@Base@,可以得知:在Derive里的,虚函数表的第二项已经被替换为&Derive::pb了,也就是被覆盖了;在调用pb函数时,根据虚函数表指针 + 表里的偏移,得到的就是派生类里的pb函数了;这里需要注意的是:虚函数表指针是怎么被赋值的,从反汇编我们可以知道,在构造函数里,会将虚函数表指针赋值。

Derive::Derive()构造函数的反汇编代码:

 1 00C12450  push        ebp  
 2 00C12451  mov         ebp,esp  
 3 00C12453  sub         esp,0CCh  
 4 00C12459  push        ebx  
 5 00C1245A  push        esi  
 6 00C1245B  push        edi  
 7 00C1245C  push        ecx  
 8 00C1245D  lea         edi,[ebp-0CCh]  
 9 00C12463  mov         ecx,33h  
10 00C12468  mov         eax,0CCCCCCCCh  
11 00C1246D  rep stos    dword ptr es:[edi]  
12 00C1246F  pop         ecx  
13 00C12470  mov         dword ptr [this],ecx  
14 00C12473  mov         ecx,dword ptr [this]  
15 00C12476  call        Base::Base (0C1150Ah)  
16 00C1247B  mov         ecx,dword ptr [this]  
17 00C1247E  add         ecx,0Ch  
18 00C12481  call        Base2::Base2 (0C114FBh)  
19 00C12486  mov         eax,dword ptr [this]  
20 00C12489  mov         dword ptr [eax],offset Derive::`vftable' (0C1AC28h)  //这个是对象的第0个位置,放置了虚函数表的地址
21 00C1248F  mov         eax,dword ptr [this]  
22 00C12492  mov         dword ptr [eax+0Ch],offset Derive::`vftable' (0C1AD54h)  //这个偏移刚好是第二个虚函数表指针的地址
23 00C12499  mov         eax,dword ptr [this]  
24 00C1249C  pop         edi  
25 00C1249D  pop         esi  
26 00C1249E  pop         ebx  
27 00C1249F  add         esp,0CCh  
28 00C124A5  cmp         ebp,esp  
29 00C124A7  call        __RTC_CheckEsp (0C11316h)  
30 00C124AC  mov         esp,ebp  
31 00C124AE  pop         ebp  
32 00C124AF  ret 

3、有虚继承的时候:

 1 class VB
 2 {
 3 public:
 4     int m_a;
 5     virtual void print() {
 6         cout << "VB" << endl;
 7     }
 8 };
 9 class Base : virtual public VB
10 {
11 public:
12     int m_b1;
13     virtual void pb()
14     {
15         cout << "pb" << endl;
16     }
17 };
18 
19 class Base2 : virtual public VB
20 {
21 public:
22     int m_b2;
23     virtual void pb2()
24     {
25         cout << "pb2" << endl;
26     }
27 };
28 
29 class Derive : public Base, public Base2
30 {
31 public:
32     int m_d;
33     virtual void pb()
34     {
35         cout << "Derive pb" << endl;
36     }
37     virtual void pb2()
38     {
39         cout << "Derive pb2" << endl;
40     }
41 };
View Code

vs里得到的对象信息:

  1 1>class VB    size(8):    //没有变化
  2 1>    +---
  3 1> 0    | {vfptr}
  4 1> 4    | m_a
  5 1>    +---
  6 1>
  7 1>VB::$vftable@:
  8 1>    | &VB_meta
  9 1>    |  0
 10 1> 0    | &VB::print
 11 1>
 12 1>VB::print this adjustor: 0
 13 1>
 14 1>class Base    size(20):    //多了一个虚基类表指针
 15 1>    +---
 16 1> 0    | {vfptr}        //虚函数表指针,size 4
 17 1> 4    | {vbptr}        //虚基类表指针,size 4
 18 1> 8    | m_b1          //size 4
 19 1>    +---
 20 1>    +--- (virtual base VB)  //大小没变化,就是布局上,被放到Base成员变量的后面了,如果有Base: virtual public vb0, virtual public vb1呢,这个虚继承的顺序将是在Base末端依次布局的顺序(见后面的补充)
 21 1>12    | {vfptr}
 22 1>16    | m_a
 23 1>    +---
 24 1>
 25 1>Base::$vftable@Base@:
 26 1>    | &Base_meta
 27 1>    |  0
 28 1> 0    | &Base::pb
 29 1>
 30 1>Base::$vbtable@:
 31 1> 0    | -4
 32 1> 1    | 8 (Based(Base+4)VB)
 33 1>
 34 1>Base::$vftable@VB@:
 35 1>    | -12
 36 1> 0    | &VB::print
 37 1>
 38 1>Base::pb this adjustor: 0
 39 1>vbi:       class  offset o.vbptr  o.vbte fVtorDisp
 40 1>              VB      12       4       4 0
 41 1>
 42 1>class Base2    size(20):
 43 1>    +---
 44 1> 0    | {vfptr}
 45 1> 4    | {vbptr}
 46 1> 8    | m_b2
 47 1>    +---
 48 1>    +--- (virtual base VB)
 49 1>12    | {vfptr}
 50 1>16    | m_a
 51 1>    +---
 52 1>
 53 1>Base2::$vftable@Base2@:
 54 1>    | &Base2_meta
 55 1>    |  0
 56 1> 0    | &Base2::pb2
 57 1>
 58 1>Base2::$vbtable@:
 59 1> 0    | -4
 60 1> 1    | 8 (Base2d(Base2+4)VB)
 61 1>
 62 1>Base2::$vftable@VB@:
 63 1>    | -12
 64 1> 0    | &VB::print
 65 1>
 66 1>Base2::pb2 this adjustor: 0
 67 1>vbi:       class  offset o.vbptr  o.vbte fVtorDisp
 68 1>              VB      12       4       4 0
 69 1>
 70 1>class Derive    size(36):
 71 1>    +---
 72 1> 0    | +--- (base class Base)//size 12,末端的虚继承信息被共享了,虚基类表指针也是在构造函数里赋值的
 73 1> 0    | | {vfptr}
 74 1> 4    | | {vbptr}
 75 1> 8    | | m_b1
 76 1>    | +---
 77 1>12    | +--- (base class Base2)
 78 1>12    | | {vfptr}
 79 1>16    | | {vbptr}
 80 1>20    | | m_b2
 81 1>    | +---
 82 1>24    | m_d
 83 1>    +---
 84 1>    +--- (virtual base VB)
 85 1>28    | {vfptr}
 86 1>32    | m_a
 87 1>    +---
 88 1>
 89 1>Derive::$vftable@Base@:
 90 1>    | &Derive_meta
 91 1>    |  0
 92 1> 0    | &Derive::pb
 93 1>
 94 1>Derive::$vftable@Base2@:
 95 1>    | -12
 96 1> 0    | &Derive::pb2
 97 1>
 98 1>Derive::$vbtable@Base@:
 99 1> 0    | -4
100 1> 1    | 24 (Derived(Base+4)VB)
101 1>
102 1>Derive::$vbtable@Base2@:
103 1> 0    | -4
104 1> 1    | 12 (Derived(Base2+4)VB)
105 1>
106 1>Derive::$vftable@VB@:
107 1>    | -28
108 1> 0    | &VB::print
109 1>
110 1>Derive::pb this adjustor: 0
111 1>Derive::pb2 this adjustor: 12
112 1>vbi:       class  offset o.vbptr  o.vbte fVtorDisp
113 1>              VB      28       4       4 0
114 1>

Derive::Derive的反汇编:

 1 00BB24E0  push        ebp  
 2 00BB24E1  mov         ebp,esp  
 3 00BB24E3  sub         esp,0CCh  
 4 00BB24E9  push        ebx  
 5 00BB24EA  push        esi  
 6 00BB24EB  push        edi  
 7 00BB24EC  push        ecx  
 8 00BB24ED  lea         edi,[ebp-0CCh]  
 9 00BB24F3  mov         ecx,33h  
10 00BB24F8  mov         eax,0CCCCCCCCh  
11 00BB24FD  rep stos    dword ptr es:[edi]  
12 00BB24FF  pop         ecx  
13 00BB2500  mov         dword ptr [this],ecx  
14 00BB2503  cmp         dword ptr [ebp+8],0  
15 00BB2507  je          Derive::Derive+48h (0BB2528h)  //这里有个判断
16 00BB2509  mov         eax,dword ptr [this]  
17 00BB250C  mov         dword ptr [eax+4],offset Derive::`vbtable' (0BBAC8Ch)  //Derive::$vbtable@Base@:
18 00BB2513  mov         eax,dword ptr [this]  
19 00BB2516  mov         dword ptr [eax+10h],offset Derive::`vbtable' (0BBAC98h)  //Derive::$vbtable@Base2@:
20 00BB251D  mov         ecx,dword ptr [this]  
21 00BB2520  add         ecx,1Ch  
22 00BB2523  call        VB::VB (0BB1267h)  //基类的构造函数
23 00BB2528  push        0  
24 00BB252A  mov         ecx,dword ptr [this]  
25 00BB252D  call        Base::Base (0BB1334h)  //基类的构造函数
26 00BB2532  push        0  
27 00BB2534  mov         ecx,dword ptr [this]  
28 00BB2537  add         ecx,0Ch  
29 00BB253A  call        Base2::Base2 (0BB1244h)  //基类的构造函数
30 00BB253F  mov         eax,dword ptr [this]  
31 00BB2542  mov         dword ptr [eax],offset Derive::`vftable' (0BBAC74h)  //虚函数表的设置则是基类构造函数call之后
32 00BB2548  mov         eax,dword ptr [this]  
33 00BB254B  mov         dword ptr [eax+0Ch],offset Derive::`vftable' (0BBAC50h)  
34 00BB2552  mov         eax,dword ptr [this]  
35 00BB2555  mov         ecx,dword ptr [eax+4]  
36 00BB2558  mov         edx,dword ptr [ecx+4]  
37 00BB255B  mov         eax,dword ptr [this]  
38 00BB255E  mov         dword ptr [eax+edx+4],offset Derive::`vftable' (0BBAC84h)  //从布局可以看到,一共有三个虚函数表指针,这是共享的那个VB的虚函数表指针的偏移
39 00BB2566  mov         eax,dword ptr [this]  
40 00BB2569  pop         edi  
41 00BB256A  pop         esi  
42 00BB256B  pop         ebx  
43 00BB256C  add         esp,0CCh  
44 00BB2572  cmp         ebp,esp  
45 00BB2574  call        __RTC_CheckEsp (0BB1325h)  
46 00BB2579  mov         esp,ebp  
47 00BB257B  pop         ebp  
48 00BB257C  ret         4  

从上面汇编分析得知,虚基类表的指针,最终指向;虚函数表指针在构造函数后赋值,最终指向派生类的虚函数表指针。

4、下面考虑如何将派生类指针转为基类指针?

  1)没有虚函数 + 没有虚继承情况下,指针偏移即可;

  2)有虚函数 + 没有虚继承,指针偏移即可,刚好虚函数指针在每个类型的第0个位置,多态执行函数很方便,可以使用dynamic_cast动态转换;

  3)没有虚函数 + 虚继承,是否可以用dynamic_cast运行期转换:不可以;

  4)有虚继承的情况,我们看下面的转换:

 1     Derive* d = new Derive();
 2 00132D78  push        24h  
 3 00132D7A  call        operator new (0131401h)  
 4 00132D7F  add         esp,4  
 5 00132D82  mov         dword ptr [ebp-11Ch],eax  
 6 00132D88  cmp         dword ptr [ebp-11Ch],0  
 7 00132D8F  je          fun2+78h (0132DC8h)  
 8 00132D91  xor         eax,eax  
 9 00132D93  mov         ecx,dword ptr [ebp-11Ch]  
10 00132D99  mov         dword ptr [ecx],eax  
11 00132D9B  mov         dword ptr [ecx+4],eax  
12 00132D9E  mov         dword ptr [ecx+8],eax  
13 00132DA1  mov         dword ptr [ecx+0Ch],eax  
14 00132DA4  mov         dword ptr [ecx+10h],eax  
15 00132DA7  mov         dword ptr [ecx+14h],eax  
16 00132DAA  mov         dword ptr [ecx+18h],eax  
17 00132DAD  mov         dword ptr [ecx+1Ch],eax  
18 00132DB0  mov         dword ptr [ecx+20h],eax  
19 00132DB3  push        1  
20 00132DB5  mov         ecx,dword ptr [ebp-11Ch]  
21 00132DBB  call        Derive::Derive (0131078h)  
22 00132DC0  mov         dword ptr [ebp-124h],eax  
23 00132DC6  jmp         fun2+82h (0132DD2h)  
24 00132DC8  mov         dword ptr [ebp-124h],0  
25 00132DD2  mov         edx,dword ptr [ebp-124h]  
26 00132DD8  mov         dword ptr [d],edx  
27     Base* b = d;
28 00132DDB  mov         eax,dword ptr [d]  //从内存布局中,我们知道Base是第一个位置的,所以直接把地址给b
29 00132DDE  mov         dword ptr [b],eax  
30     Base2* b2 = d;
31 00132DE1  cmp         dword ptr [d],0  
32 00132DE5  je          fun2+0A5h (0132DF5h)  
33 00132DE7  mov         eax,dword ptr [d]  
34 00132DEA  add         eax,0Ch              //Base2在Derive中的偏移是12
35 00132DED  mov         dword ptr [ebp-124h],eax  
36 00132DF3  jmp         fun2+0AFh (0132DFFh)  
37 00132DF5  mov         dword ptr [ebp-124h],0  
38 00132DFF  mov         ecx,dword ptr [ebp-124h]  
39 00132E05  mov         dword ptr [b2],ecx  
40     VB* vb = d;
41 00132E08  cmp         dword ptr [d],0  
42 00132E0C  jne         fun2+0CAh (0132E1Ah)  
43 00132E0E  mov         dword ptr [ebp-124h],0  
44 00132E18  jmp         fun2+0E0h (0132E30h)  
45 00132E1A  mov         eax,dword ptr [d]  
46 00132E1D  mov         ecx,dword ptr [eax+4]  //虚基类表指针,Derive::$vbtable@Base@
47 00132E20  mov         edx,dword ptr [ecx+4]  //得到偏移,24
48 00132E23  mov         eax,dword ptr [d]  
49 00132E26  lea         ecx,[eax+edx+4]      //this + 偏移24 + 4 =this+28,刚好等于VB在Derive中的对象内存位置
50 00132E2A  mov         dword ptr [ebp-124h],ecx  
51 00132E30  mov         edx,dword ptr [ebp-124h]  
52 00132E36  mov         dword ptr [vb],edx  
53     VB*vb1 = b;
54 00132E39  cmp         dword ptr [b],0  
55 00132E3D  jne         fun2+0FBh (0132E4Bh)  
56 00132E3F  mov         dword ptr [ebp-124h],0  
57 00132E49  jmp         fun2+111h (0132E61h)  
58 00132E4B  mov         eax,dword ptr [b]  
59 00132E4E  mov         ecx,dword ptr [eax+4]  
60 00132E51  mov         edx,dword ptr [ecx+4]  
61 00132E54  mov         eax,dword ptr [b]  
62 00132E57  lea         ecx,[eax+edx+4]  
63 00132E5B  mov         dword ptr [ebp-124h],ecx  
64 00132E61  mov         edx,dword ptr [ebp-124h]  
65 00132E67  mov         dword ptr [vb1],edx  
66     VB*vb2 = b2;
67 00132E6A  cmp         dword ptr [b2],0  
68 00132E6E  jne         fun2+12Ch (0132E7Ch)  
69 00132E70  mov         dword ptr [ebp-124h],0  
70     VB*vb2 = b2;
71 00132E7A  jmp         fun2+142h (0132E92h)  
72 00132E7C  mov         eax,dword ptr [b2]  
73 00132E7F  mov         ecx,dword ptr [eax+4]  
74 00132E82  mov         edx,dword ptr [ecx+4]  
75 00132E85  mov         eax,dword ptr [b2]  
76 00132E88  lea         ecx,[eax+edx+4]  
77 00132E8C  mov         dword ptr [ebp-124h],ecx  
78 00132E92  mov         edx,dword ptr [ebp-124h]  
79 00132E98  mov         dword ptr [vb2],edx  

5、多个虚继承下的内存对象信息:

 1 class VB
 2 {
 3 public:
 4     int m_a;
 5     virtual void print() {
 6         cout << "VB" << endl;
 7     }
 8 };
 9 class VB2
10 {
11 public:
12     int m_a2;
13     virtual void print2() {
14         cout << "VB" << endl;
15     }
16 };
17 class Base : virtual public VB,virtual public VB2
18 {
19 public:
20     int m_b1;
21     virtual void pb()
22     {
23         cout << "pb" << endl;
24     }
25 };
View Code
 1 1>class VB    size(8):
 2 1>    +---
 3 1> 0    | {vfptr}
 4 1> 4    | m_a
 5 1>    +---
 6 1>
 7 1>VB::$vftable@:
 8 1>    | &VB_meta
 9 1>    |  0
10 1> 0    | &VB::print
11 1>
12 1>VB::print this adjustor: 0
13 1>
14 1>class VB2    size(8):
15 1>    +---
16 1> 0    | {vfptr}
17 1> 4    | m_a2
18 1>    +---
19 1>
20 1>VB2::$vftable@:
21 1>    | &VB2_meta
22 1>    |  0
23 1> 0    | &VB2::print2
24 1>
25 1>VB2::print2 this adjustor: 0
26 1>
27 1>class Base    size(28):
28 1>    +---
29 1> 0    | {vfptr}
30 1> 4    | {vbptr}
31 1> 8    | m_b1
32 1>    +---
33 1>    +--- (virtual base VB)
34 1>12    | {vfptr}
35 1>16    | m_a
36 1>    +---
37 1>    +--- (virtual base VB2)
38 1>20    | {vfptr}
39 1>24    | m_a2
40 1>    +---
41 1>
42 1>Base::$vftable@Base@:
43 1>    | &Base_meta
44 1>    |  0
45 1> 0    | &Base::pb
46 1>
47 1>Base::$vbtable@:
48 1> 0    | -4
49 1> 1    | 8 (Based(Base+4)VB)
50 1> 2    | 16 (Based(Base+4)VB2)
51 1>
52 1>Base::$vftable@VB@:
53 1>    | -12
54 1> 0    | &VB::print
55 1>
56 1>Base::$vftable@VB2@:
57 1>    | -20
58 1> 0    | &VB2::print2
59 1>
60 1>Base::pb this adjustor: 0
61 1>vbi:       class  offset o.vbptr  o.vbte fVtorDisp
62 1>              VB      12       4       4 0
63 1>             VB2      20       4       8 0
64 1>
View Code

可以看到Base里只有一个虚基类表指针,且指向的内容是:

1>Base::$vbtable@:
1> 0 | -4
1> 1 | 8 (Based(Base+4)VB)
1> 2 | 16 (Based(Base+4)VB2)

原文地址:https://www.cnblogs.com/mingbujian/p/13984419.html