C++虚继承下的内存模型(1)
1、对于普通继承,基类子对象始终位于派生类对象的前面(也即基类成员变量始终在派生类成员变量的前面),而且不管继承层次有多深,它相对于派生类对象顶部的偏移量是固定的。请看下面的例子:

2、obj_a、obj_b、obj_c、obj_d 的内存模型如下所示
A 是最顶层的基类,在派生类 B、C、D 的对象中,A 类子对象始终位于最前面,偏移量是固定的,为 0。b1、b2 是派生类 B 的新增成员变量,它们的偏移量也是固定的,分别为 8 和 12。c1、c2、d1、d2 也是同样的道理。

3、前面我们说过,编译器在知道对象首地址的情况下,通过计算偏移来存取成员变量。对于普通继承,基类成员变量的偏移是固定的,不会随着继承层级的增加而改变,存取起来非常方便。
而对于虚继承,恰恰和普通继承相反,大部分编译器会把基类成员变量放在派生类成员变量的后面,这样随着继承层级的增加,基类成员变量的偏移就会改变,就得通过其他方案来计算偏移量。下面我们来一步一步地分析虚继承时的对象内存模型。
4、1) 修改上面的代码,使得 A 是 B 的虚基类:
class B: virtual public A;
此时 obj_b、obj_c、obj_d 的内存模型就会发生变化,如下图所示:
不管是虚基类的直接派生类还是间接派生类,虚基类的子对象始终位于派生类对象的最后面。

5、2) 再假设 A 是 B 的虚基类,B 又是 C 的虚基类,那么各个对象的内存模型如下图所示

6、从上面的两张图中可以发现,虚继承时的派生类对象被分成了两部分:
不带阴影的一部分偏移量固定,不会随着继承层次的增加而改变,称为固定部分;
带有阴影的一部分是虚基类的子对象,偏移量会随着继承层次的增加而改变,称为共享部分。
7、当要访问对象的成员变量时,需要知道对象的首地址和变量的偏移,对象的首地址很好获得,关键是变量的偏移。对于固定部分,偏移是不变的,很好计算;而对于共享部分,偏移会随着继承层次的增加而改变,这就需要设计一种方案,在偏移不断变化的过程中准确地计算偏移。
对于虚继承,将派生类分为固定部分和共享部分,并把共享部分放在最后,几乎所有的编译器都在这一点上达成了共识。主要的分歧就是如何计算共享部分的偏移,可谓是百花齐放,没有统一标准。
各个编译器正是在设计这一方案时出现了分歧,不同的编译器设计了不同的方案来计算共享部分的偏移。
8、到底不同的编译器是怎么解决共享部分的偏移的呢?让我们下节课再来看吧!