类实例和虚函数表¶
为了理解类实例与 VF 表之间的关系,让我们检查这个清单中声明的三个对象的内存转储:
这些对象是清单 4-5 中定义的类型。你可以在图 4-4 中看到它们在内存中的样子。
窗格 1 显示每个类实例像结构一样存储成员,但在它们之前有一个指向类实例 VF 表的 DWORD 值。窗格 2 显示我们三个类实例各自的 VF 表。表 4-4 中的内存到代码对照展示了这些窗格和代码如何联系在一起。
窗格地址大小数据对象窗格 1

窗格地址大小数据对象窗格 1
窗格 1
窗格 1
窗格 1
窗格 1
窗格 1 窗格 2
窗格 2
窗格 2
窗格 2
窗格 2
窗格 2
窗格 2
窗格 2
窗格 2
窗格 2
窗格 2
这份对照展示了清单 4-5 中代码的 VF 表如何在内存中布局。每个 VF 表在二进制生成时由编译器创建,而且表保持不变。为了节省空间,同一类的实例都指向同一个 VF 表,这就是为什么 VF 表不与类内联放置。
既然有三个 VF 表,你可能想知道类实例如何知道使用哪个 VF 表。编译器在每个虚类构造函数中放置类似下面这段汇编的代码:
这个示例取 VF 表的静态地址(VFADDR),把它作为类的第一个成员放入内存。
现在看看表 4-4 中的地址 0x004022B0、0x004022C0 和 0x004022D0。这些地址包含 foo、fooa 和 foob VF 表的开头。注意 foo::barbaz 存在于所有三个 VF 表中;这是因为该函数没有被任何子类重载,意味着每个子类的实例都会直接调用原始实现。
还要注意,foo::myStaticValue 没有出现在这份对照中。由于该值是静态的,它实际上不需要作为 foo 类的一部分存在;它放在这个类中只是为了更好的代码组织。实际上,它被当作全局变量放在其他地方。