C++ 虚拟继承对象布局

自己做实验的时候发现和一些文章上面的有点不一样。

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
#include <iostream>
using namespace std;


class B
{
public:
long ib;
long cb;//全都定义成 long 这个方便看内存,不然不是对齐的
public:
B() :ib(0x1010), cb(0x1010) {}
virtual void f() { cout << "B::f()" << endl; }
virtual void Bf() { cout << "B::Bf()" << endl; }
};

class B1 : virtual public B
{
public:
long ib1;
long cb1;
public:
B1() :ib1(0x1111111), cb1(0x1111) {}
//virtual void f() { cout << "B1::f()" << endl; } //为什么要注释掉呢,后面说,不注释掉是失败的 是运行不了的
virtual void f1() { cout << "B1::f1()" << endl; }
virtual void Bf1() { cout << "B1::Bf1()" << endl; }
};


int main()
{
typedef void(*Fun)(void);
long long** pVtab = NULL;
Fun pFun = NULL;
B1 bb1;
cout<<hex;
pVtab = (long long**)&bb1;
cout<<"offset:"<<(long)pVtab-(long)(B*)&bb1<<endl;//因为存在偏移 (B*)&bb1 是不等于 &bb1 这个地址的。
cout << "[0] B1::_vptr->" << endl;
for(int i=0;(Fun) pVtab[0][i]!=NULL;i++){
cout<<"\t["<<i<<"]\t";
pFun=(Fun)pVtab[0][i];
pFun();
}
cout << "[2] B1::ib1 = ";
cout << (long)pVtab[1]<< endl; //B1::ib1
cout << "[3] B1::cb1 = ";
cout << (long)pVtab[2] << endl; //B1::cb1

cout << "[3] B::_vptr->" << endl;
for(int i=0;i<2;i++){ //就是这个地方,如果前面不注释掉就会报错
//还有你没有没有注意到细节 ,我这个地方写的是 <2 并不是 函数为 空?
cout<<"\t["<<i<<"]\t";
pFun=(Fun)(pVtab[3][i]);
pFun();
}
cout << "[4] B::ib1 = ";
cout << (long)pVtab[4]<< endl; //B1::ib1
cout << "[5] B::cb1 = ";
cout << (long)*(pVtab+5) << endl; //B1::cb1
return 0;
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
offset:ffffffffffffffe8
[0] B1::_vptr->
[0] B1::f1()
[1] B1::Bf1()
[2] B1::ib1 = 1111111
[3] B1::cb1 = 1111
[3] B::_vptr->
[0] B::f()
[1] B::Bf()
[4] B::ib1 = 1010
[5] B::cb1 = 1010

GDB 调试 查看内存,有意思的来了。

1
2
3
4
5
6
7
8
(gdb) p pVtab
$1 = (long long **) 0x7ffdf9b52fc0 //一个B1对象的具体内存
(gdb) x /10xg 0x7ffdf9b52fc0
0x7ffdf9b52fc0: 0x0000562080dd6d08(这个是第一个虚表指针) 0x0000000001111111
0x7ffdf9b52fd0: 0x0000000000001111 0x0000562080dd6d38(第二个虚表指针)
0x7ffdf9b52fe0: 0x0000000000001010 0x0000000000001010
0x7ffdf9b52ff0: 0x0000562080bd61c0 0x0000562080bd617e
0x7ffdf9b53000: 0x00007ffdf9b52fc0 0x0000000200000000

有的文章里面说,虚拟继承每个基类会创建一个虚表,实际上并没有,只是多了一个虚表指针并没有那么多神奇的玩意。
你可以发现两个地址挨着非常近。我们查看 0x0000562080dd6d08这个的内存。

1
2
3
4
5
6
gdb) x /10xg 0x0000562080dd6d08
0x562080dd6d08 <vtable for B1+24>: 0x0000562080bd6146 0x0000562080bd617e
0x562080dd6d18 <vtable for B1+40>: 0x0000000000000000 0x0000000000000000
0x562080dd6d28 <vtable for B1+56>: 0xffffffffffffffe8 0x0000562080dd6d78
0x562080dd6d38 <vtable for B1+72>: 0x0000562080bd607e(第二个虚表指针在这???) 0x0000562080bd60b6
0x562080dd6d48 <VTT for B1>: 0x0000562080dd6d08(有没有发现这个地址就是第一个虚表指针,并没有NULL,所以我那个地方写的是i<2) 0x0000562080dd6d38

看到了吗,实际上全都在B1的虚表里面,只是指针指向的位置不一样。还有个更有意思的。
有没有发现有两个0x0000000000000000目前不知道是啥。0xffffffffffffffe8这个刚好就是偏移量,并没有什么指针直接指向他。所以我不知道虚基表指针是啥,也没看到一个指针算进对象的空间。

然后再继续看这两个虚表指针的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
(gdb) x /10xg 0x0000562080bd6146 //不出意外是个函数
0x562080bd6146 <B1::f1()>: 0x10ec8348e5894855 0xfb358d48f87d8948
0x562080bd6156 <B1::f1()+16>: 0x0f003d8d48000000 0x48fffff97be80020
0x562080bd6166 <B1::f1()+32>: 0x200e89058b48c289 0xe8d78948c6894800
0x562080bd6176 <B1::f1()+48>: 0x90c3c990fffff976 0x10ec8348e5894855
0x562080bd6186 <B1::Bf1()+8>: 0xcc358d48f87d8948 0x0ec83d8d48000000

(gdb) x /10xg 0x0000562080bd607e //第二个指针也是
0x562080bd607e <B::f()>: 0x10ec8348e5894855 0xb4358d48f87d8948
0x562080bd608e <B::f()+16>: 0x0fc83d8d48000001 0x48fffffa43e80020
0x562080bd609e <B::f()+32>: 0x200f51058b48c289 0xe8d78948c6894800
0x562080bd60ae <B::f()+48>: 0x90c3c990fffffa3e 0x10ec8348e5894855
0x562080bd60be <B::Bf()+8>: 0x83358d48f87d8948 0x0f903d8d48000001

然后就是更有意思的。记得我们原本注释的吗,我们把他取消注释再来GDB调试。

1
2
3
(gdb) x /10xg 0x000055aec160519d //因为重新运行了地址不一样,我们像那样看第二个虚指针第一项的值
0x55aec160519d <virtual thunk to B1::f()>: 0xebe87a0349178b4c 0xec8348e5894855c0
//看到了 virtual thunk ,这个告诉我们这个已经被重写了,然后又会调用B1::f()

是不是发现这个布局很像这种写法。

1
2
3
class B{
A a;
}

当然只是有点像,到了菱形继承就差距很大了。我们继续探索。

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
105
106
107
108
109
110
111
112
113
114
115
116
#include <iostream>
using namespace std;


#include <iostream>
using namespace std;
class B
{
public:
long ib;

public:
B() : ib(0xbbbbb)
{}

virtual void
f()
{
cout << "B::f()" << endl;
}
virtual void Bf() { cout << "B::Bf()" << endl; }
};

class B1 : virtual public B
{
public:
long ib1;

public:
B1() : ib1(0x1111)
{
}
virtual void f(){cout << "B1::f()" << endl;}
virtual void f1() { cout << "B1::f1()" << endl; }
virtual void Bf1() { cout << "B1::Bf1()" << endl; }
};

class B2 : virtual public B
{
public:
long ib2;

public:
B2() : ib2(0x2222)
{
}
// virtual void f(){cout << "B2::f()" << endl;}
virtual void f2() { cout << "B2::f2()" << endl; }
virtual void Bf2() { cout << "B2::Bf2()" << endl; }
};

class D : public B1,
public B2
{
public:
long id;
public:
D() : id(0xdddd)
{
}
virtual void
f()
{
cout << "D::f()" << endl;
}
virtual void f1() { cout << "D::f1()" << endl; }
virtual void f2() { cout << "D::f2()" << endl; }
virtual void Df() { cout << "D::Df()" << endl; }
};


int main()
{
typedef void(*Fun)(void);
long long** pVtab = NULL;
Fun pFun = NULL;
D d;

cout<<"D size:"<<sizeof(d)<<endl;
cout<<"B1 size:"<<sizeof(B1)<<endl;
cout<<"D address:"<<&d<<endl;
cout<<"B1 address:"<<(B1*)&d<<endl;
cout<<"B1-B2 offset:"<<(long)(B1*)&d-(long)(B2*)&d<<endl;
cout<<"B2-B offset:"<<(long)(B2*)&d-(long)(B*)&d<<endl;
cout<<"B address:"<<(B*)&d<<endl;
pVtab = (long long**)&d;
cout<<hex;
cout << "[0] B1::_vptr->" << endl;
for(int i=0;i<5;i++){
cout<<"\t["<<i<<"]\t";
pFun=(Fun)pVtab[0][i];
pFun();
}
cout << "[1] B1::ib1 = ";
cout << (long)pVtab[1]<< endl;
cout << "[2] B2::_vptr->" << endl;
for(int i=0;i<2;i++){
cout<<"\t["<<i<<"]\t";
pFun=(Fun)pVtab[2][i];
pFun();
}
cout << "[3] B2::ib2 = ";
cout << (long)pVtab[3]<< endl;

cout << "[4] B::ib = ";
cout << (long)pVtab[4]<< endl;
cout << "[5] B::_vptr->" << endl;
for(int i=1;i<2;i++){
cout<<"\t["<<i<<"]\t";
pFun=(Fun)pVtab[5][i];
pFun();
}
cout << "[6] B::ib = ";
cout << (long)pVtab[6]<< endl;
return 0;
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
D size:56
B1 size:32
D address:0x7fff13a26610
B1 address:0x7fff13a26610
B1-B2 offset:-16
B2-B offset:-24
B address:0x7fff13a26638
[0] B1::_vptr->
[0] D::f()
[1] D::f1()
[2] B1::Bf1()
[3] D::f2()
[4] D::Df()
[1] B1::ib1 = 1111
[2] B2::_vptr->
[0] D::f2()
[1] B2::Bf2()
[3] B2::ib2 = 2222
[4] B::ib = dddd
[5] B::_vptr->
[1] B::Bf()
[6] B::ib = bbbbb

GDB查看内存。

1
2
3
4
5
6
(gdb) x /10xg pVtab
0x7ffc64cadc10: 0x000055c32e665b88(B1的虚指针) 0x0000000000001111
0x7ffc64cadc20: 0x000055c32e665bc8(B2的虚指针) 0x0000000000002222
0x7ffc64cadc30: 0x000000000000dddd(D的数据) 0x000055c32e665bf8(B的虚指针)
0x7ffc64cadc40: 0x00000000000bbbbb 0x0000000000000000
0x7ffc64cadc50: 0x0000000000000000 0x00007ffc64cadc10

看到这应该都清楚了。显然B布局到了最后面。大概就是因为这个所以才能实现只有一份拷贝吧。具体详细分析,自己进去看吧,就是把东西结合起来。
为了方便查看偏移量 我GDB 调试 10进制

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
(gdb) x /10g pVtab
0x7ffd2687ea70: 94162329979784 4369
0x7ffd2687ea80: 94162329979848 8738
0x7ffd2687ea90: 56797 94162329979896
0x7ffd2687eaa0: 768955 0
0x7ffd2687eab0: 0 140725249895024
(gdb) x /10dg 94162329979784
0x55a3e03acb88 <vtable for D+24>: 94162327877818 94162327877882
0x55a3e03acb98 <vtable for D+40>: 94162327877436 94162327877938
0x55a3e03acba8 <vtable for D+56>: 94162327878000 24(不知道具体怎么排列的,但是知道有就行了)
0x55a3e03acbb8 <vtable for D+72>: -16 94162329980184
0x55a3e03acbc8 <vtable for D+88>: 94162327877993 94162327877624

(gdb) x /10dg 94162329979848
0x55a3e03acbc8 <vtable for D+88>: 94162327877993 94162327877624
0x55a3e03acbd8 <vtable for D+104>: 0 -40
0x55a3e03acbe8 <vtable for D+120>: -40 94162329980184
0x55a3e03acbf8 <vtable for D+136>: 94162327877873 94162327877184
0x55a3e03acc08 <VTT for D>: 94162329979784 94162329979992

(gdb) x /10dg 94162329979896
0x55a3e03acbf8 <vtable for D+136>: 94162327877873 94162327877184
0x55a3e03acc08 <VTT for D>: 94162329979784 94162329979992
0x55a3e03acc18 <VTT for D+16>: 94162329980048 94162329980088
0x55a3e03acc28 <VTT for D+32>: 94162329980136 94162329979896
0x55a3e03acc38 <VTT for D+48>: 94162329979848 40

看了好多博客,都搞不懂,自己做出来的和他们有点不一样。
结论你们自己得吧,告辞。