放置外部虚拟表

Placing of external virtual tables

本文关键字:虚拟 外部      更新时间:2023-10-16

摘自Large-Scale C++ Software Design (Lakos),第652页:

问题是,"编译器会将给定类的虚拟表定义存放在哪个唯一的转换单元中?CFRONT(以及许多其他C++实现)采用的技巧是将外部虚拟表放在翻译单元中,该转换单元定义类中出现的词法上第一个非内联函数(如果存在)。

最常用的编译器(GCC 和 Visual C++)仍然如此吗?还是曾经?

GCC 碰巧记录了它的行为如问题 (http://gcc.gnu.org/onlinedocs/gcc/Vague-Linkage.html) 中所述:

VTables

C++虚函数在大多数编译器中使用查找表(称为 vtable)实现。vtable 包含指向类提供的虚函数的指针,并且类的每个对象都包含一个指向其 vtable(在某些多重继承情况下或 vtables)的指针。如果类声明任何非内联、非纯虚函数,则选择第一个作为类的"key 方法",并且 vtable 仅在定义 key 方法的转换单元中发出。

注意:如果所选键方法稍后定义为内联,则 vtable 仍将在定义它的每个翻译单元中发出。确保在类主体中以内联方式声明任何内联虚拟,即使它们未在此处定义。

但是,即使在多个对象文件中可能有多个 vtables 的情况下(如果"key 方法"结果是内联的,则可能会发生这种情况),编译器也会安排尽可能忽略重复项,但如果目标不支持 COMDAT,则重复项最终可能会在最终二进制文件中使用空格:

当与 GNU ld 2.8 或更高版本一起使用时,例如 GNU/Linux 或 Solaris 2,或在 Microsoft Windows 上,复制副本 这些构造将在链接时被丢弃。这被称为 COMDAT 支持。

在不支持 COMDAT 但支持弱符号的目标上,GCC 将使用它们。这样,一个副本将覆盖所有其他副本,但是 未使用的副本仍将占用可执行文件中的空间。

对于不支持 COMDAT 或弱符号的目标,大多数 具有模糊链接的实体将作为局部符号发出以避免 链接器中的重复定义错误。这不会发生在 但是,内联中的本地静态数据,因为具有多个副本将 几乎可以肯定是破坏了东西。

FWIW,GCC 似乎使用以 __ZTV 开头的符号表示 vtable。

就 MSVC 而言,使用 VC++10 进行的一些实证测试(我不认为 MS 记录了该行为)表明,VC 似乎并没有尝试将 vtable 限制为单个对象文件。由于Microsoft知道它可以依赖于支持 COMDAT 部分的链接器,并且由于构造函数是唯一直接使用 vtable 的函数(我相信所有其他 vtable 使用都是间接的,通过对象指针),看起来 VC 只是将 vtable 的副本放在实例化构造函数的任何对象文件中。 对于使用编译器生成的 ctor 的类,这将是构造该类型对象的任何位置。