C++进阶 类的内存结构

117 阅读3分钟

环境: 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.

image.png

2.带非虚方法的数据类:

class B{int a ; void Fun(){}};

B类长度和A类一样,也就是说,类方法没有保存在类对象里.

image.png

3.带虚方法的类

class C{int a; virtual void Fun(){}};

C类长度为16(以8对齐),该指针指向虚函数表.

虚函数表:

image.png

0 offset_to_top 指向虚函数表的虚函数指针在对象中的偏移量 1 C RTTI 运行时信息,dynamic_cast的实现原理就是使用它 -- (C,0) vtable address --

类结构:

image.png

4.继承非虚方法的数据类

class B1 : public B {int b;};

image.png

父类数据在前,子类数据在后

5.继承虚方法的数据类

class C1 : public C{int b; void Fun(){}};

虚函数表:

image.png

可以看到C1的虚函数表里是C1::Fun,这就是多态的原理

类结构:

image.png

不管怎么继承,第一个肯定都是虚函数指针.

6 多重继承

class BC : public B , public C{int c;};

这里B类和C类都有一个int成员a,所以BC对象不能直接.a,会提示歧义,将BC对象转成对应父类调取对应a. 至于为什么编译通过估计是BC里面的两个a被编译器符号修饰了,带上了他们父类的类名,.a出不来因为编译器判断不了拿哪个基类类名修饰a的符号.

虚函数表:

image.png

类结构:

image.png

7 多重继承,两个带虚函数的父类

class D{int a ; virtual void Method(){}};

class CD : public C , public D{};

虚函数表:

image.png

不管有几个带虚方法的父类,派生类对象都只有一个虚函数表,但是RTTI信息会随之增长,谁是主类应该与顺序有关。

类结构:

image.png

有几个带虚方法的父类就有几个虚函数表指针

8 虚拟继承

class VB : virtual public B{};

虚基类对象表:

image.png 0 vbase_offset 虚基类对象数据在派生类对象中的偏移 类结构:

image.png

注意,父类B没有虚方法,派生类VB也没有虚方法,但是派生类却有一个虚指针,该指针指向虚基类对象表

9 多重虚拟继承带虚方法的类

class VBC : virtual public C , virtual public B{} ;

虚基类对象和虚函数合一表:

image.png

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 {};

虚函数表:

image.png

和上面想的一样,两个父类,所以两个vbase_offset; 两个父类都有虚方法,所以两个vcall_offset,下面都跟两个offset_to_top,代表各自的虚方法表指针的偏移

类结构:

image.png

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{};

虚函数表:

image.png

类结构:

image.png

总结:

类的内存结构(简单的就不说了,默认带继承的):

0 第一个带虚方法的父类 0 虚函数表/虚对象表指针 8 父类数据


x 第n个带虚方法的父类 x 虚函数表/虚对象表指针 x+8 父类数据


y 派生类 y 派生类数据(派生类这边没有虚函数表/虚对象表指针,它的这些东西在第一个父类的虚函数表/虚对象表里,也许是合并了)

注意对齐问题,每个类的数据是分别对齐的!