【问题标题】:How is virtual function table generated in subclass子类中如何生成虚函数表
【发布时间】:2020-03-26 22:06:34
【问题描述】:

所以我知道在 c++ 中,虚拟方法是针对存储在表中的每个类,并且每个实例都有一个指向该表的指针。所以我的问题是子类表的样子。我将提供一个汇编指令:

vtable for Derived:
    .long   0
    .long   _typeinfo for Derived
    .long   _Derived::set()
    .globl  _vtable for Base
    .section    .rdata$vtable for Base,"dr"
    .linkonce same_size
    .align 4

所以,从这里我可以看到 Derived 有一个虚拟方法,即set(),但让我烦恼的部分是 Base 的 vtable。 Derived vptr 是否持有指向 Base vptr 的指针,或者它是否存储在 Derived 的 vtable 中。 我注意到在代码编译器中只将 vtable 存储在对象的 0 地址,一次在 Base 构造函数中,一次在 Derived 中。为什么 vtable 没有被覆盖?

附:我不太懂指令

编辑:

class Base{
   virtual void print() {
       printf("Base");
   }
}

class Derived : Base{
   virtual void print(){
       printf("Derived");
   }
}

【问题讨论】:

  • 也许它被覆盖了。但是,如果先存储基本存储,然后再存储派生,则它会被正确的值覆盖。
  • @ErikEidt 至少在 g++ 代码中,构造函数将 vtable 指针写入对象。所以基础构造函数将基础 vtable 写入对象,然后派生构造函数用派生 vtable 覆盖它。
  • 我对指令了解不多,但我认为.globl 只是符号可见性的问题; vtable for Derived 仅在存储信息方面首先由树构成 .long
  • 他问的问题对我来说很清楚:“编译器只是将 vtable 存储在对象的 0 地址,一次在 Base 构造函数中,一次在 Derived 中。为什么不覆盖 vtable?” 。他已经看到两个构造函数都写入同一个位置。我解释了它是如何工作的。
  • @Jester 是的,你明白我的要求,抱歉问题的格式不好。谢谢你的澄清。

标签: c++ assembly polymorphism vtable


【解决方案1】:

简单的答案是:这取决于编译器。标准没有具体说明这应该如何实现,只说明它应该如何表现。

在实践中,实现倾向于简单地具有一个派生 vtable,它以与基本 vtable 相同的结构开始,而不是在末尾附加派生类方法(也就是说,派生类中的新方法,而不是覆盖)。

vtable 指针只是指向整个表的开头。如果对象是通过基指针类型访问的,那么任何人都不应该超越基类方法的末尾。

如果指针是派生类型,那么同一个指针将允许在表中进一步访问派生类中声明的虚方法。

附录:多重继承

多重继承遵循相同的基本概念,但由于显而易见的原因很快变得复杂。但有一个重要特征需要牢记。

多重派生类对于它的每个基类都有一个vtable指针,指向同一个vtable中的不同vtable或不同位置(取决于实现)。

但重要的是要记住它是每个直接基类每个对象一个。

因此,如果您有一个多重派生类,其中包含一个 int 数据和三个直接基类,则每个对象的大小实际上是 16 字节(在 32 位系统上;在 64 位系统上更多)。 int 有 4 个,每个 vtable 指针有 4 个。此外,当然还有每个基类本身的大小。

这意味着在 C++ 中,接口并不便宜。 (很明显,C++ 中没有真正的接口,而是一个没有数据且只有纯虚方法的基类模拟它。)每个这样的接口都花费一个指针的大小每个对象

在像 C# 和 Java 这样的语言中,接口是语言的一部分,有一种稍微不同的机制,即所有接口都通过单个 vtable 指针进行路由。这稍微慢了一点,但意味着每个对象只有一个 vtable 指针,但是实现了许多接口。

出于设计原因,我仍然会在 C++ 中遵循接口样式方法,但请始终注意这种额外开销。

(而且这些都没有涉及到虚拟继承。)

【讨论】:

  • 我认为这是有道理的,所以基本上如果我有 3 个不同的派生类,每个类都会有 Base vtable 附加它自己的表,我假设对于每个进一步的子类,该子类只会附加它自己的桌子?
  • 对于多重继承,还涉及到更多的曲折。然而仍然是编译器实现细节,而不是应该在代码中利用的东西(它是非常不稳定的代码,并且不可移植)。
  • @Eljay 上帝是的!多重继承是可怕的,尽管我在我的回答中添加了一个重要的警告。
  • @AntonioAntolcic:related:How do objects work in x86 at the assembly level? 展示了一些真正的编译器为 x86 生成的 asm,用于访问数据成员和创建虚函数的一些最简单的情况。我想你已经知道了很多,但未来的读者可能会受益。很好的继承总结,Jasper,+1
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-02
  • 2018-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-05
相关资源
最近更新 更多