盒子
盒子
文章目录
  1. 测试环境
  2. 其它问题
    1. 1. 为什么类B和C有自己的虚函数的时候,会增加一个vfptr,而D有自己的虚函数的时候不会再增加一个vfptr?
  3. 2. 如何直接调用一个虚函数

类的内存布局

探讨C++中的类的内存布局,对于这个问题,网上可以搜索到很多的文章了。但是自己还是打算测试一下,权当是检验真理。

测试环境

  • VS2017 (Debug模式下x86)

本文主要是对多继承的测试,首先看一下代码.

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
class A {
public:
virtual void funcA() {}
private:
int a;
};



class B : virtual public A {

public:
virtual void funcA() {}
virtual void funcB() {}
private:
int b;
};


class C : virtual public A {
public:
virtual void funcA() {}
virtual void funcC() {}
private:
int c;
};


class D : public B, public C {
public:
virtual void funcA() {}
virtual void funcD() {}
private:
int d;
};

/*
A
/ \
B C
\ /
D
类的继承体系如上,虚继承。
*/
int main()
{
return 0;
}

在VS中添加编译选项/d1 reportAllClassLayout 得到如下的结果:

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
class A	size(8):
+---
0 | {vfptr}
4 | a
+---

A::$vftable@:
| &A_meta
| 0
0 | &A::funcA

A::funcA this adjustor: 0

class B size(20):
+---
0 | {vfptr}
4 | {vbptr}
8 | b
+---
+--- (virtual base A)
12 | {vfptr}
16 | a
+---

B::$vftable@B@:
| &B_meta
| 0
0 | &B::funcB

B::$vbtable@:
0 | -4
1 | 8 (Bd(B+4)A)

B::$vftable@A@:
| -12
0 | &B::funcA

B::funcA this adjustor: 12
B::funcB this adjustor: 0
vbi: class offset o.vbptr o.vbte fVtorDisp
A 12 4 4 0

class C size(20):
+---
0 | {vfptr}
4 | {vbptr}
8 | c
+---
+--- (virtual base A)
12 | {vfptr}
16 | a
+---

C::$vftable@C@:
| &C_meta
| 0
0 | &C::funcC

C::$vbtable@:
0 | -4
1 | 8 (Cd(C+4)A)

C::$vftable@A@:
| -12
0 | &C::funcA

C::funcA this adjustor: 12
C::funcC this adjustor: 0
vbi: class offset o.vbptr o.vbte fVtorDisp
A 12 4 4 0

class D size(36):
+---
0 | +--- (base class B)
0 | | {vfptr}
4 | | {vbptr}
8 | | b
| +---
12 | +--- (base class C)
12 | | {vfptr}
16 | | {vbptr}
20 | | c
| +---
24 | d
+---
+--- (virtual base A)
28 | {vfptr}
32 | a
+---

D::$vftable@B@:
| &D_meta
| 0
0 | &B::funcB
1 | &D::funcD

D::$vftable@C@:
| -12
0 | &C::funcC

D::$vbtable@B@:
0 | -4
1 | 24 (Dd(B+4)A)

D::$vbtable@C@:
0 | -4
1 | 12 (Dd(C+4)A)

D::$vftable@A@:
| -28
0 | &D::funcA

D::funcA this adjustor: 28
D::funcD this adjustor: 0
vbi: class offset o.vbptr o.vbte fVtorDisp
A 28 4 4 0

由上可知:

  1. 存在虚函数的类中都会有vfptr(指向虚函数表)
  2. 从虚基类继承的时候,虚基类排在最后面
  3. 虚继承利用到了vbptr,若不是虚继承,则会重复包含父类成员

其它问题

1. 为什么类B和C有自己的虚函数的时候,会增加一个vfptr,而D有自己的虚函数的时候不会再增加一个vfptr?

个人理解是因为类会把自己的虚函数加入到第一个vfptr指向的虚函数表中去,因为类B第一个成员不是vfptr,所以编译器自动加了一个vfptr…纯属个人理解。

2. 如何直接调用一个虚函数

一个简单示例:

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
#include <iostream>
#include <cstdlib>

class Test {
public:
virtual void func1() {
std::cout << "hello Test\n";
}

virtual int func2() {
return 1;
}
};

int main()
{
Test a;
//通过类型转换获得虚表的地址
auto vtable = (intptr_t*)(*(intptr_t*)(&a));

auto func1 = (void(*)())vtable[0];
auto func2 = (int(*)())vtable[1];
func1();
std::cout << func2() << std::endl;

system("pause");

return 0;
}

通过直接获得类中的vfptr中的地址,然后再通过类型的转换,获得虚函数表的地址,然后再从虚函数表中获得函数地址,最后调用即可。进行类型转换的原因是因为需要对指针解引用,而指针所指向的类型影响到了解引用时的内存大小,所以需要进行合理地类型转换,否则会出错的。

想啥呢
想啥呢