【问题标题】:Placing of external virtual tables放置外部虚拟表
【发布时间】:2012-08-15 00:21:22
【问题描述】:

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

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

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

【问题讨论】:

  • 这纯粹是实现定义的。编译器甚至可能没有虚拟表或虚拟指针,并以自己喜欢的方式实现动态调度。
  • @Als:哦,是的,编译器可以做的事情是无限多的。然而,这个问题很实际,这是列出的编译器实际所做的。
  • @Als "编译器甚至可能没有虚拟表或虚拟指针,并以自己喜欢的方式实现动态调度。" 尽管如此,实际上,每个现实世界的编译器都使用vtables,vptr...
  • +1 这是一个有效的问题。不是最关键或最引人入胜的 C++,而是关于现实世界实现的一个有效问题。
  • 现代链接器通常具有合并相同符号定义的多个副本的能力,因此是否为同一类生成多个虚拟表副本不再那么重要;链接时它们很可能会合并。但是,如果析构函数是虚拟的(因此必须生成一个外行版本),将虚拟表放在析构函数所在的位置是最有意义的。当然,在析构函数的外联版本中,如果析构函数被声明为内联,则可能在多个翻译单元中存在副本。

标签: c++ visual-c++ gcc compilation vtable


【解决方案1】:

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

VTables

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

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

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

在 ELF 系统上与 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 的类,它可以是构造该类型对象的任何位置。

【讨论】:

  • 构造函数是唯一的函数”带有一些(不是全部)析构函数?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-12
  • 2019-08-29
  • 2011-06-04
  • 1970-01-01
相关资源
最近更新 更多