环境: 64位主机 linux操作系统 g++编译器
PS:使用clang打印类及虚函数表结构的命令:
clang -cc1 -stdlib=libc++ -fdump-record-layouts -fdump-vtable-layouts -emit-llvm ./main.cpp > classhierarchy
1.纯数据类:
class A{int a;};
纯数据类在内存中的存储方式等同于struct.
2.带非虚方法的数据类:
class B{int a ; void Fun(){}};
B类长度和A类一样,也就是说,类方法没有保存在类对象里.
3.带虚方法的类
class C{int a; virtual void Fun(){}};
C类长度为16(以8对齐),该指针指向虚函数表.
虚函数表:
0 offset_to_top 指向虚函数表的虚函数指针在对象中的偏移量 1 C RTTI 运行时信息,dynamic_cast的实现原理就是使用它 -- (C,0) vtable address --
类结构:
4.继承非虚方法的数据类
class B1 : public B {int b;};
父类数据在前,子类数据在后
5.继承虚方法的数据类
class C1 : public C{int b; void Fun(){}};
虚函数表:
可以看到C1的虚函数表里是C1::Fun,这就是多态的原理
类结构:
不管怎么继承,第一个肯定都是虚函数指针.
6 多重继承
class BC : public B , public C{int c;};
这里B类和C类都有一个int成员a,所以BC对象不能直接.a,会提示歧义,将BC对象转成对应父类调取对应a. 至于为什么编译通过估计是BC里面的两个a被编译器符号修饰了,带上了他们父类的类名,.a出不来因为编译器判断不了拿哪个基类类名修饰a的符号.
虚函数表:
类结构:
7 多重继承,两个带虚函数的父类
class D{int a ; virtual void Method(){}};
class CD : public C , public D{};
虚函数表:
不管有几个带虚方法的父类,派生类对象都只有一个虚函数表,但是RTTI信息会随之增长,谁是主类应该与顺序有关。
类结构:
有几个带虚方法的父类就有几个虚函数表指针
8 虚拟继承
class VB : virtual public B{};
虚基类对象表:
0 vbase_offset 虚基类对象数据在派生类对象中的偏移
类结构:
注意,父类B没有虚方法,派生类VB也没有虚方法,但是派生类却有一个虚指针,该指针指向虚基类对象表
9 多重虚拟继承带虚方法的类
class VBC : virtual public C , virtual public B{} ;
虚基类对象和虚函数合一表:
0 vbase_offset (16) C基类对象数据的偏移 1 vbase_offset (8) B基类对象数据的偏移 //肯定带虚方法的父类是主类 2 offset_to_top (0) 指向虚基类对象表的虚对象指针的偏移
4 vcall_offset (0) 指向虚函数表的虚函数表指针的偏移 和虚对象指针是同一个 5 offset_to_top (-16) 指向父类虚函数表的虚函数表指针的偏移
类结构:
10 双虚基类
class VCD : virtual public C , virtual public D {};
虚函数表:
和上面想的一样,两个父类,所以两个vbase_offset; 两个父类都有虚方法,所以两个vcall_offset,下面都跟两个offset_to_top,代表各自的虚方法表指针的偏移
类结构:
11 终极 菱形继承/钻石继承
class A {int a ; virtual void Fun(){}};
class B : virtual public A {int b ;};
class C : virtual public A {int b ;};
class D : public B , public C{};
虚函数表:
类结构:
总结:
类的内存结构(简单的就不说了,默认带继承的):
0 第一个带虚方法的父类 0 虚函数表/虚对象表指针 8 父类数据
x 第n个带虚方法的父类 x 虚函数表/虚对象表指针 x+8 父类数据
y 派生类 y 派生类数据(派生类这边没有虚函数表/虚对象表指针,它的这些东西在第一个父类的虚函数表/虚对象表里,也许是合并了)
注意对齐问题,每个类的数据是分别对齐的!