为什么需要虚拟thunk?

时间:2017-06-06 18:43:44

标签: c++ g++ vtable thunk

这个问题是关于虚拟函数调用的(可能的)实现(我认为它由gcc使用)。

请考虑以下情况:

  1. F类继承自继承自B类(非虚拟)的D类(也可能是其他类)。 D覆盖B中声明的虚方法f(); F类型的对象被实例化
  2. 类F继承自继承自B类(虚拟)的D类(可能还有其他类)。 D覆盖B中声明的虚方法f(); F类型的对象被实例化
  3. (这两种情况之间的唯一区别是B类被继承的方式)

    在方案1中,在对象B的vtable中,在f()的位置,现在有一个(非虚拟)thunk:

      

    如果您想致电f(),请先使用this更改offset指针

    (实际上是D把那个thunk放在那里)

    在方案2中,在对象B的vtable中,在f()的位置,现在有一个(虚拟)thunk:

      

    如果您想致电f(),请先使用this

    中存储的值更改addr指针

    (D无法确切地告诉B需要调整多少this指针,因为它不知道B对象在F对象的最终内存布局中的位置。

    这些假设是通过查看g++ -fdump-class-hierarchyg++ -S的输出结果来做出的。它们是否正确?

    现在我的问题是:为什么需要虚拟 thunk?为什么F不能在B的虚拟表中放置非虚拟 thunk(在f()的位置)?因为当需要实例化F对象时,编译器知道在B中声明了f(),但它在D中被覆盖了。它还知道对象B(-in-F)和对象B之间的确切偏移量。对象D(-in-F)(我认为首先是虚拟 thunk的原因)。

    编辑(添加了g++ -fdump-class-hierarchyg++ -S的输出)

    情景1:

    g++ -fdump-class-hierarchy

      

    F表格

         

    ...

         

    48(int(*)(...))D :: _ZThn8_N1D1fEv (去除错误:非虚拟thunk到D :: f())

    g++ -S

      

    _ZThn8_N1D1fEv

         

    .LFB16:

         

    .cfi_startproc

         

    subq $ 8,%rdi#,

         

    jmp .LTHUNK0#

         

    .cfi_endproc

    情景2:

    g++ -fdump-class-hierarchy

      

    F表格

         

    ...

         

    64(int(*)(...))D :: _ZTv0_n24_N1D1fEv (de-mangled:虚拟thunk到D :: f())

    g++ -S

      

    _ZTv0_n24_N1D1fEv

         

    .LFB16:

         

    .cfi_startproc

         

    movq(%rdi),%r​​10#,

         

    addq -24(%r10),%rdi#,

         

    jmp .LTHUNK0#

         

    .cfi_endproc

1 个答案:

答案 0 :(得分:3)

我想我找到了答案here

  

" ...鉴于上述信息,有几种可能的实施方式。请注意,在下面我们假设在调用任何vtable条目之前,已经调整了this指针以指向与从中提取vptr的vtable对应的子对象。

     

一个。由于偏移总是在编译时已知,即使对于虚拟基础,每个thunk也可能是不同的,将已知偏移添加到此并分支到目标函数。   这将导致每个覆盖者在不同的偏移处产生一个thunk。因此,每次在代码中的任何给定点为参考更改实际类型时,都会发生分支错误预测和可能的指令高速缓存未命中。

     

B中。在虚拟继承的情况下,虽然在声明覆盖器时已知,但是偏移量可能根据来自覆盖器类的派生而不同。上面的H和I是最简单的例子。 H是I的主要基础,但I的int成员意味着A在I中与H的偏移量与从独立H中的H不同。因此,ABI指定虚拟基础A的辅助vtable包含到H的vcall偏移量,以便共享thunk可以加载vcall偏移量,将其添加到此,并分支到目标函数H :: f。   这将导致更少的thunk,因为对于继承层次结构,其中A是H的虚拟基础,并且H :: f重写A :: f,更大层次结构中的H的所有实例都可以使用相同的thunk。结果,这些thunk将导致更少的分支错误预测和指令缓存未命中。权衡是他们必须在偏移添加之前进行加载。由于偏移量小于thunk的代码,因此负载应该不经常在缓存中丢失,因此尽管vcall偏移负载需要2个或更多个周期,但更好的缓存未命中行为应该会产生更好的结果....&# 34;

似乎虚拟thunk仅出于性能原因而存在。如果我错了,请纠正我。