5

来自大型 C++ 软件设计(Lakos),第 652 页:

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

最常用的编译器(GCC 和 Visual C++)仍然是这种情况吗?还是曾经?

4

1 回答 1

7

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

虚拟表

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

注意:如果选择的键方法后来被定义为内联,则 vtable 仍将在定义它的每个翻译单元中发出。确保任何内联虚拟都在类主体中声明为内联,即使它们没有在那里定义。

然而,即使在多个目标文件中可能存在多个 vtable 的情况下(如果“关键方法”被证明是内联的,则可能发生这种情况),编译器会尽可能安排忽略重复项,但重复项可能会结束如果目标不支持 COMDAT,则在最终二进制文件中使用空格:

在 GNU/Linux 或 Solaris 2 等 ELF 系统或 Microsoft Windows 上与 GNU ld 版本 2.8 或更高版本一起使用时,这些结构的重复副本将在链接时被丢弃。这称为 COMDAT 支持。

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

对于不支持 COMDAT 或弱符号的目标,大多数具有模糊链接的实体将作为本地符号发出,以避免来自链接器的重复定义错误。然而,内联中的局部静态不会发生这种情况,因为拥有多个副本几乎肯定会破坏事情。

FWIW,GCC 似乎使用以__ZTVvtable 开头的符号。

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

于 2012-08-19T10:27:36.517 回答