测试代码如下:
1#include <stdio.h>
2
3class A
4{
5public:
6 int a;
7 virtual ~A(void){}
8};
9class B : virtual public A
10{
11};
12class C : virtual public A
13{
14 virtual void FuncC(void) {}
15};
16class D : public B, public C
17{
18};
19class E : public B
20{
21 virtual void FuncE(void) {}
22};
23
24int main(int argc, char** argv)
25{
26 A a;
27 B b;
28 C c;
29 D d;
30 E e;
31 printf("%d %d %d %d %d\n",
32 sizeof(A), sizeof(B), sizeof(C), sizeof(D), sizeof(E));
33
34 return 0;
35}
1、VC
VC根据是否为虚继承来判断是否在继承关系中共享虚函数表指针。使用下面的命令即可查看源文件main.cpp中类X的内存分布。结果中vfptr为虚函数表指针,vbptr为虚基类指针。(cl版本信息为Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23026 for x86)
1cl main.cpp /d1reportSingleClassLayoutX
(1)A
1class A size(8):
2 +---
3 0 | {vfptr}
4 4 | a
5 +---
(2)B
1class B size(12):
2 +---
3 0 | {vbptr}
4 +---
5 +--- (virtual base A)
6 4 | {vfptr}
7 8 | a
8 +---
(3)C。和B对比我们可以看出,只有子类有新的虚函数时,编译器才会在子类中添加虚表指针。
1class C size(16):
2 +---
3 0 | {vfptr}
4 4 | {vbptr}
5 +---
6 +--- (virtual base A)
7 8 | {vfptr}
812 | a
9 +---
(4)D
1class D size(20):
2 +---
3 | +--- (base class C)
4 0 | | {vfptr}
5 4 | | {vbptr}
6 | +---
7 | +--- (base class B)
8 8 | | {vbptr}
9 | +---
10 +---
11 +--- (virtual base A)
1212 | {vfptr}
1316 | a
14 +---
(5)E
1class E size(16):
2 +---
3 0 | {vfptr}
4 | +--- (base class B)
5 4 | | {vbptr}
6 | +---
7 +---
8 +--- (virtual base A)
9 8 | {vfptr}
1012 | a
11 +---
2、GCC
GCC中虚继承的子类和父类是共享虚函数指针的。
运行下面的命令可以查看源文件main.cpp中所有类的内存分布情况。运行命令后会生成一个名为main.cpp.002t.class。这个文件中包了类的内存分布,虚表内容等信息。注意,需要较高版本的gcc。(g++版本为g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-4),系统为CentOS 7.2.1511 x86_64)
1g++ -fdump-class-hierarchy main.cpp
2cat main.cpp.002t.class
(1)A
1Class A
2 size=16 align=8
3 base size=12 base align=8
4A (0x0x7fc56a101960) 0
5 vptr=((& A::_ZTV1A) + 16u)
(2)B
1Class B
2 size=24 align=8
3 base size=8 base align=8
4B (0x0x7fc56a0f6138) 0 nearly-empty
5 vptridx=0u vptr=((& B::_ZTV1B) + 24u)
6 A (0x0x7fc56a1019c0) 8 virtual
7 vptridx=8u vbaseoffset=-24 vptr=((& B::_ZTV1B) + 64u)
(3)C
1Class C
2 size=24 align=8
3 base size=8 base align=8
4C (0x0x7fc56a0f62d8) 0 nearly-empty
5 vptridx=0u vptr=((& C::_ZTV1C) + 24u)
6 A (0x0x7fc56a101a20) 8 virtual
7 vptridx=8u vbaseoffset=-24 vptr=((& C::_ZTV1C) + 72u)
(4)D
1Class D
2 size=32 align=8
3 base size=16 base align=8
4D (0x0x7fc56a1339a0) 0
5 vptridx=0u vptr=((& D::_ZTV1D) + 24u)
6 B (0x0x7fc56a0f63a8) 0 nearly-empty
7 primary-for D (0x0x7fc56a1339a0)
8 subvttidx=8u
9 A (0x0x7fc56a101a80) 16 virtual
10 vptridx=40u vbaseoffset=-24 vptr=((& D::_ZTV1D) + 112u)
11 C (0x0x7fc56a0f6410) 8 nearly-empty
12 subvttidx=24u vptridx=48u vptr=((& D::_ZTV1D) + 64u)
13 A (0x0x7fc56a101a80) alternative-path
(5)E
1Class E
2 size=24 align=8
3 base size=8 base align=8
4E (0x0x7fc56a0f6680) 0 nearly-empty
5 vptridx=0u vptr=((& E::_ZTV1E) + 24u)
6 B (0x0x7fc56a0f66e8) 0 nearly-empty
7 primary-for E (0x0x7fc56a0f6680)
8 subvttidx=8u
9 A (0x0x7fc56a101ae0) 8 virtual
10 vptridx=24u vbaseoffset=-24 vptr=((& E::_ZTV1E) + 72u)
3、Clang
Clang中虚继承的子类和父类是共享虚函数指针的。
Clang可以使用下面的命令查看main.cpp文件中所有结构的内存分布。结果在stdout中。(Clang版本为clang version 3.4.2 (tags/RELEASE_34/dot2-final)Target: x86_64-redhat-linux-gnu,系统为CentOS 7.2.1511 x86_64)
1clang -Xclang -fdump-record-layouts
(1)A
1*** Dumping AST Record Layout
2 0 | class A
3 0 | (A vtable pointer)
4 8 | int a
5 | [sizeof=16, dsize=12, align=8
6 | nvsize=12, nvalign=8]
(2)B
1*** Dumping AST Record Layout
2 0 | class B
3 0 | (B vtable pointer)
4 8 | class A (virtual base)
5 8 | (A vtable pointer)
6 16 | int a
7 | [sizeof=24, dsize=20, align=8
8 | nvsize=8, nvalign=8]
(3)C
1*** Dumping AST Record Layout
2 0 | class C
3 0 | (C vtable pointer)
4 8 | class A (virtual base)
5 8 | (A vtable pointer)
6 16 | int a
7 | [sizeof=24, dsize=20, align=8
8 | nvsize=8, nvalign=8]
(4)D
1*** Dumping AST Record Layout
2 0 | class D
3 0 | class B (primary base)
4 0 | (B vtable pointer)
5 8 | class C (base)
6 8 | (C vtable pointer)
7 16 | class A (virtual base)
8 16 | (A vtable pointer)
9 24 | int a
10 | [sizeof=32, dsize=28, align=8
11 | nvsize=16, nvalign=8]
(5)E
1*** Dumping AST Record Layout
2 0 | class E
3 0 | class B (primary base)
4 0 | (B vtable pointer)
5 8 | class A (virtual base)
6 8 | (A vtable pointer)
7 16 | int a
8 | [sizeof=24, dsize=20, align=8
9 | nvsize=8, nvalign=8]
结论:
1、虚基类指针
VC、GCC和Clang这三个编译器的实现中,不管是否是虚继承还是有虚函数,其虚基类指针都不共享,都是单独的。
2、虚函数表指针
(1)VC根据是否为虚继承来判断是否在继承关系中共享虚表指针。如果子类是虚继承拥有虚函数父类,且子类有新加的虚函数时,子类中则会新加一个虚函数表指针。
(2)GCC和Clang的虚表指针在整个继承关系中共享的。