使用命令
g++ | g++ -fdump-lang-class main.cpp |
VS | cl /d1 reportAllClassLayout main.cpp |
以下以g++环境进行分析
单继承
class Base
{
public:
Base(int a = 1, int b = 2){};
virtual void fun1();
virtual void func2(){};
private:
int a = 1;
int b = 1;
};
class Derive : public Base
{
public:
Derive(int a = 1, int b = 2) : Base(a, b){};
private:
int a = 1;
int b = 1;
};
通过实践 g++ -fdump-lang-class main.cpp,如图注释,vptr确切翻译应该为虚指针, 指向第一个虚函数地址而不是虚函数表地址。vptr指向的是虚函数表的首地址地址+16的位置,这不正好是func的地址么。
Vtable for Base //Base的虚函数表
Base::_ZTV4Base: 4 entries
0 (int (*)(...))0 //top_offset多继承情况下使用
8 (int (*)(...))(& _ZTI4Base) //RTTI类型,typeid(), dynamic_cast使用的就是这个
16 (int (*)(...))Base::fun1
24 (int (*)(...))Base::func2
Class Base
size=16 align=8
base size=16 base align=8
Base (0x0x7f04d7259420) 0
vptr=((& Base::_ZTV4Base) + 16)
Vtable for Derive //Derive的虚函数表
Derive::_ZTV6Derive: 4 entries
0 (int (*)(...))0 //top_offset多继承情况下使用
8 (int (*)(...))(& _ZTI6Derive) //RTTI类型,typeid(), dynamic_cast使用的就是这个
16 (int (*)(...))Base::fun1
24 (int (*)(...))Base::func2
Class Derive
size=24 align=8 //对齐后的总大小,内存对齐大小
base size=24 base align=8 //没有对齐的情况下的大小
Derive (0x0x7f04d71051a0) 0
vptr=((& Derive::_ZTV6Derive) + 16) //虚函数指针,指向Derive虚函数表的第一个虚函数
Base (0x0x7f04d7259660) 0
primary-for Derive (0x0x7f04d71051a0)
多继承
class Base
{
public:
Base(int a = 1, int b = 2){};
virtual void fun1();
virtual void func2(){};
private:
int a = 1;
int b = 1;
};
class Base1
{
public:
Base1(int a = 1, int b = 2){};
virtual void fun3();
virtual void func4(){};
private:
int a = 1;
int b = 1;
};
class Base2
{
public:
Base2(int a = 1, int b = 2){};
virtual void fun3();
virtual void func4(){};
private:
};
class Derive : public Base, Base1, Base2
{
public:
Derive(int a = 1, int b = 2) : Base(a, b), Base1(a, b), Base2(a, b){};
private:
int a = 1;
int b = 1;
};
通过 g++ -fdump-lang-class main.cpp, 结果如图,多继承的情况下的内存布局,这里我使用的是64位服务器,所以vptr是8字节,内存对齐也是按8字节对齐。
内存布局和虚函数表的top-offset偏移量可以看出,内存是先存储第一直接基类的vptr(覆盖后)和成员变量,然后是以此是第二和第三基类的vptr和成员变量,这里的虚函数表就填入了top-offset,他就是Base1:vptr的偏移量等等。
Vtable for Base
Base::_ZTV4Base: 4 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI4Base)
16 (int (*)(...))Base::fun1
24 (int (*)(...))Base::func2
Class Base
size=16 align=8
base size=16 base align=8
Base (0x0x7fc61fd92420) 0
vptr=((& Base::_ZTV4Base) + 16)
Vtable for Base1
Base1::_ZTV5Base1: 4 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5Base1)
16 (int (*)(...))Base1::fun3
24 (int (*)(...))Base1::func4
Class Base1
size=16 align=8
base size=16 base align=8
Base1 (0x0x7fc61fd92660) 0
vptr=((& Base1::_ZTV5Base1) + 16)
Vtable for Base2
Base2::_ZTV5Base2: 4 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5Base2)
16 (int (*)(...))Base2::fun3
24 (int (*)(...))Base2::func4
Class Base2
size=8 align=8
base size=8 base align=8
Base2 (0x0x7fc61fd928a0) 0 nearly-empty
vptr=((& Base2::_ZTV5Base2) + 16)
Vtable for Derive
Derive::_ZTV6Derive: 12 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI6Derive)
16 (int (*)(...))Base::fun1
24 (int (*)(...))Base::func2
32 (int (*)(...))-16
40 (int (*)(...))(& _ZTI6Derive)
48 (int (*)(...))Base1::fun3
56 (int (*)(...))Base1::func4
64 (int (*)(...))-32
72 (int (*)(...))(& _ZTI6Derive)
80 (int (*)(...))Base2::fun3
88 (int (*)(...))Base2::func4
Class Derive
size=48 align=8 //48可以看出是有三个vptr
base size=48 base align=8
Derive (0x0x7fc61fdaa348) 0
vptr=((& Derive::_ZTV6Derive) + 16) //第一个虚指针
Base (0x0x7fc61fd92ae0) 0
primary-for Derive (0x0x7fc61fdaa348)
Base1 (0x0x7fc61fd92b40) 16 //这里的16对象内存偏移量
vptr=((& Derive::_ZTV6Derive) + 48) //第二个虚指针
Base2 (0x0x7fc61fd92ba0) 32 nearly-empty //这里的32对象内存偏移量
vptr=((& Derive::_ZTV6Derive) + 80) //第三个个虚指针
引入虚继承,菱形继承
突然发现这个博主已经有总结的了,且总结的内容跟我实验的过程差不多,就不写重复工作了。