类的内存布局
2018.04.28
mrsiz
 热度
℃
探讨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
| 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; };
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
|
由上可知:
- 存在虚函数的类中都会有vfptr(指向虚函数表)
- 从虚基类继承的时候,虚基类排在最后面
- 虚继承利用到了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中的地址,然后再通过类型的转换,获得虚函数表的地址,然后再从虚函数表中获得函数地址,最后调用即可。进行类型转换的原因是因为需要对指针解引用,而指针所指向的类型影响到了解引用时的内存大小,所以需要进行合理地类型转换,否则会出错的。