【发布时间】:2017-06-06 18:43:44
【问题描述】:
这个问题是关于虚函数调用的(可能的)实现(我相信它被 gcc 使用)。
考虑以下场景:
- F 类继承自 D 类(可能还有其他),而 D 类继承自 B 类(不是虚拟的)。 D覆盖了B中声明的虚方法
f();实例化一个 F 类型的对象 - F 类继承自 D 类(可能还有其他),而 D 类继承自 B 类(实际上)。 D覆盖了B中声明的虚方法
f();实例化一个 F 类型的对象
(这两种情况的唯一区别是B类的继承方式)
在场景 1 中,在对象 B 的 vtable 中,在指向 f() 的位置现在有一个 (non-virtual) thunk 表示:
如果要调用
f(),先将this指针改为offset
(实际上是 D 把这个 thunk 放在那里)
在场景 2 中,在对象 B 的 vtable 中,在指向 f() 的位置现在有一个 (virtual) thunk 表示:
如果要调用
f(),首先将this指针更改为存储在addr的值
(D无法准确告诉B需要调整多少this指针,因为它不知道B对象在F对象最终内存布局中的位置)
这些假设是通过结合g++ -S 查看g++ -fdump-class-hierarchy 的输出得出的。他们是对的吗?
现在我的问题是:为什么需要虚拟 thunk?为什么 F 不能在 B 的虚拟表(在f() 的位置)中放置一个非虚拟 thunk?因为当需要实例化一个 F 对象时,编译器知道 f() 在 B 中声明,但它在 D 中被覆盖。并且它还知道对象 B(-in-F)和对象 D 之间的确切偏移量(-in-F) (我认为这首先是 virtual thunk 的原因)。
编辑(添加g++ -fdump-class-hierarchy 和g++ -S 的输出)
场景 1:
g++ -fdump-class-hierarchy:
F的Vtable
...
48 (int (*)(...))D::_ZThn8_N1D1fEv(de-mangled:非虚拟 thunk 到 D::f())
g++ -S:
_ZThn8_N1D1fEv:
.LFB16:
.cfi_startproc
subq $8, %rdi #,
jmp .LTHUNK0 #
.cfi_endproc
场景 2:
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
【问题讨论】:
-
您能否用一个简洁的代码示例以及
g++ -fdump-class-hierarchy和g++ -S的输出来说明您的问题。很难从你的散文中理解你的意思。另请注意,vtable 不是 C++ 标准概念,而是编译器特定的实现细节。 -
我现在会尝试快速重新创建它。
-
现代编译器现在非常擅长去虚拟化函数。但有时,如果有超出他们控制范围的外部功能依赖于虚拟性,他们就无法做到。
-
@πάνταῥεῖ 当问题包括三个不同的实现特定标签(g++、vtable 和 thunk 不是 C++ 概念)。
-
@curiousguy 我不知道:-P