前言

想要了解一下虚继承的内部数据结构

(语言: c++, 编译器: g++(4.8.5))

 

内存布局

考虑以下代码

单纯的虚拟继承而已, 我使用了这种方式打印它们的内部数据

然后它们A, B, C, D内部数据情况如下

emmm...

OK, 我可以把前两个 4 字节认为是虚指针(A没有)

那么A, B, C的内存分布就不用看了, 唯一比较特殊的是虚基类在内存的高地址

然后D的内部分布... 这... emmm... 仔细一想, 还好, 重要的是(Q1: 为什么最后补了一个0 ??)

 

存取方式

我试着访问了一下数据

来看一看汇编

数据的访问在编译时就已经定好的, 不存在额外效率影响

A指针 偏移了 +32 字节 直接访问数据

B指针 未偏移 访问数据时跳过了虚指针(8字节)

C指针 偏移 +16 字节 访问数据时跳过了虚指针(8字节)

D指针 未偏移 访问数据时跳过了虚指针 + B, C类数据(共28字节)

或许看一下访问虚基类的数据会有所了解?

emmm... 有一个重大的发现就是, 基类对象前面有数据

这些数据应该是偏移, 这些偏移配合 + 指针本身的地址能够访问到正确的虚基类数据

也就是说, 这是 深入探索对象 中, 对于虚基类的两种实现方式中的第一种

即: 在每个基类中添加虚基类的偏移, 但是又不全对...

越来越糊涂了 (눈_눈), 深吸一口气, 想想自己要干嘛... emmmm....

这样吧, 完整地看一遍构造的过程, 这样应该就明白了

(如果还是晕, 那么就希望下次遇到这个问题的时候能够更从容一些)

(我已经再这个问题上花太多时间, 再这样下去反而不好, 这也不是一个很重要/常用的知识)

 

完整的构造过程

首先是 main 区块

然后是D的构造函数:

A的构造函数:

B的构造函数:

C的构造函数:

也就是说, 在D的构造中, ABC的构造都被调用了

要注意的一点是, BC都未调用A的构造, A的构造仅在D中调用了一次

同时要注意的是, BC会用那个编译器给的数字往自身的内存布局中写值

但是如果其上有D, D会将那个值覆写一次, 假设其值恒定不变, 位移分别为:

8 16 24 48

这是一组有规律的数字, 以 8 为开始, 每个数字是前面数字之和(这个就是偏移)

剩下来最重要的是, D覆写了什么?

 

再次来看看是如何访问虚基类数据的

汇编:

这次要清晰得多了, 我们以上面D的内存布局为例:

那么在代码中试一下, 如果没有错误的话, 那个 x 应该是 4

(这个地址明显非常低, 它甚至没达到 8 字节, 这应该是编译的时候准备好的数据)

取出来了 16, emmm... 嗯, 没错, 4 个 4字节 4 x 4 = 16 !!!!!!!

PS: 有没有思考过为什么通过双间接才能去到虚基类呢?

这里明显的一个问题是, 虚基类的数据是通过 C 的某个地址索引到 D 的某个地址

然后再用 C 的地址索引到虚基类数据, 有两次的跳转

我再次试了一下有继承三个虚基类的情况(就是D再继承了一个类似 BC的类)

发现它的访问方式是一样的, 也就是说, 每一个自身都会往前寻找固定的字节(编译后固定)

然后用那个地址的数据 + 指针本身的地址(我不知道我又没有表达清楚, 不过我感觉我没有 :) )

内存布局:

这样的内存布局在其他方面可能还会有用

那么为什么会有 0 呢? 我估计是因为要实现这种内存布局, 或者说什么其他原因

 

summary

我很怀疑下次我看笔记时, 我自己看不看得懂我在说什么...