这个问题是关于虚函数调用的(可能的)实现(我相信它被使用gcc
)。
考虑以下场景:
- F 类继承自 D 类(可能还有其他),而 D 类继承自 B 类(不是虚拟的)。D 覆盖
f()
B 中声明的虚方法;实例化一个 F 类型的对象 - F 类继承自 D 类(可能还有其他),而 D 类继承自 B 类(虚拟)。D 覆盖
f()
B 中声明的虚方法;实例化一个 F 类型的对象
(这两种情况的唯一区别是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-hierarchy
结合的输出而做出的g++ -S
。他们是正确的吗?
现在我的问题是:为什么需要虚拟thunk?为什么 F 不能在 B 的虚拟表中(在 for 的位置)放置一个非虚拟f()
thunk ?因为当需要实例化 F 对象时,编译器知道它f()
在 B 中声明,但在 D 中被覆盖。它还知道对象 B (-in-F) 和对象 D (-in -F)(我认为这首先是虚拟重击的原因)。
编辑(添加g++ -fdump-class-hierarchy
和的输出g++ -S
)
场景一:
g++ -fdump-class-hierarchy
:
F 的 Vtable
...
48 (int (*)(...))D:: _ZThn8_N1D1fEv (de-mangled: non-virtual thunk to D::f())
g++ -S
:
_ZThn8_N1D1fEv:
.LFB16:
.cfi_startproc
subq $8, %rdi #,
jmp .LTHUNK0 #
.cfi_endproc
场景二:
g++ -fdump-class-hierarchy
:
F 的 Vtable
...
64 (int (*)(...))D:: _ZTv0_n24_N1D1fEv (de-mangled: virtual thunk to D::f())
g++ -S
:
_ZTv0_n24_N1D1fEv:
.LFB16:
.cfi_startproc
movq (%rdi), %r10 #,
addq -24(%r10), %rdi #,
jmp .LTHUNK0 #
.cfi_endproc