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
class Base {
public:
int baseValue;
virtual void baseFun() {}
};

class B {
public:
int bValue;
virtual void fb1() {}
virtual void fb2() {}
virtual void test() { cout << "B::test()" << endl; }
};

class C : public Base {
public:
int cValue;
virtual void fc1() {}
virtual void fc2() {}
virtual void test() { cout << "C::test()" << endl; }
};

class A : public B, public C
{
public:
int aPublicValue;
virtual void fb2() {}
//virtual void test() { cout << "A::test()" << endl; }
virtual void fc2() {}
virtual void funcA1() {}
virtual void funcA2() {}
void funcA3() {}
private:
short aPrivateValue;
};

C++对象内存布局

通过VisualStudio来查看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
class A	size(28):
+---
0 | +--- (base class B)
0 | | {vfptr}
4 | | bValue
| +---
8 | +--- (base class C)
8 | | +--- (base class Base)
8 | | | {vfptr}
12 | | | baseValue
| | +---
16 | | cValue
| +---
20 | aPublicValue
24 | aPrivateValue
| <alignment member> (size=2)
+---

A::$vftable@B@:
| &A_meta
| 0
0 | &B::fb1
1 | &A::fb2
2 | &B::test
3 | &A::funcA1
4 | &A::funcA2

A::$vftable@C@:
| -8
0 | &Base::baseFun
1 | &C::fc1
2 | &A::fc2
3 | &C::test

A::fb2 this adjustor: 0
A::fc2 this adjustor: 8
A::funcA1 this adjustor: 0
A::funcA2 this adjustor: 0

A::$vftable@B@可以看出虚函数表中A和B合并在一起了,并且A::fb2覆盖了B::fb2。而C类的vftable则是独立存储。

B和C两个类都定义了test()函数,而A类则没有覆盖test方法,如果A类调用test方法到底采用的是那个呢?在A::$vftable@B@指向的是B::test。而A::$vftable@C@指向的是C::test。我们通过下面代码进行测试:

1
2
3
4
5
6
7
8
9
10
int main() {
A a;
//a.test(); //此处会导致编译错误,因为无法确定test的实现
a.B::test(); //通过限定引用可以解决多义性问题
B& b = static_cast<B&>(a);
b.test(); //打印 B::test()
C* c = static_cast<C*>(&a);
c->test(); //打印 C::test()
return 0;
}

所以基于A类的实例是无法调用test函数的,但是把A类转换成B/C的引用或者指针后,就能调用test方法了,但只能分别调用自己的函数实现。

我们现在把C继承Base使用virtual进行修饰,代码改动如下:

1
class C : public virtual Base

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
class A	size(36):
+---
0 | +--- (base class B)
0 | | {vfptr}
4 | | bValue
| +---
8 | +--- (base class C)
8 | | {vfptr}
12 | | {vbptr}
16 | | cValue
| +---
20 | aPublicValue
24 | aPrivateValue
| <alignment member> (size=2)
+---
+--- (virtual base Base)
28 | {vfptr}
32 | baseValue
+---

A::$vftable@:
| &A_meta
| 0
0 | &B::fb1
1 | &A::fb2
2 | &B::test
3 | &A::funcA1
4 | &A::funcA2

A::$vftable@C@:
| -8
0 | &C::fc1
1 | &A::fc2
2 | &C::test

A::$vbtable@:
0 | -4
1 | 16 (Ad(C+4)Base)

A::$vftable@Base@:
| -28
0 | &Base::baseFun

A::fb2 this adjustor: 0
A::fc2 this adjustor: 8
A::funcA1 this adjustor: 0
A::funcA2 this adjustor: 0
vbi: class offset o.vbptr o.vbte fVtorDisp
Base 28 12 4 0

可以看出class c中多了一个{vbptr}指针,这个叫做虚基类指针。

查看类内存布局的方法

使用VisualStudio

在VisualStudio中:工具 > 命令行 > 开发者命令行提示,然后输入命令:

1
cl /d1 reportAllClassLayout xxx.cpp > class.txt

输入内容比较多,搜索“class ClassName”就可以找到对应类的内部布局信息。

使用clang

1
2
clang -Xclang -fdump-record-layouts -stdlib=libc++  -std=gnu++11 xxx.cpp
clang -Xclang -fdump-vtable-layouts -stdlib=libc++ -std=gnu++11 -I/usr/include/c++/11 -I/usr/include/x86_64-linux-gnu/c++/11 -c xxx.cpp

这种方式好像在继承关系使用virtual修饰时没有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
*** Dumping AST Record Layout
0 | class A
0 | class B (primary base)
0 | (B vtable pointer)
8 | int bValue
16 | class C (base)
16 | (C vtable pointer)
24 | int cValue
28 | int aPublicValue
32 | short aPrivateValue
36 | class Base (virtual base)
36 | int baseValue
| [sizeof=40, dsize=40, align=8,
| nvsize=34, nvalign=8]

参考

多个基类