提问人:Enlico 提问时间:9/18/2023 最后编辑:Enlico 更新时间:9/18/2023 访问量:83
为什么虚拟的成员函数会影响实际上不需要虚拟调度的 TU 的编译代码?
Why a member function being virtual affects the compiled code of a TU where no virtual dispatch is actually required?
问:
在这样的 TU 中
#include "Foo.hpp"
int main() {
// stuff
Foo* foo{new Foo{}};
foo->foo();
// stuff
}
其中包含Foo.hpp
#pragma once
struct Foo {
virtual void foo(); // implmented somewhere
};
除了可能发生之外,没有其他任何事情,对吧? 是 也不是 它 也不是 类 是 ,所以是的,在另一个 TU 中可能有派生类的对象,依此类推,但是......就这个 TU 而言,我认为很明显,调用 .我不明白怎么会是这样。Foo::foo
foo
virtual
final
override
foo
foo->foo()
Foo::foo()
那为什么生成的程序集是这样的呢?
main: # @main
push rax
mov edi, 8
call operator new(unsigned long)@PLT
mov rcx, qword ptr [rip + vtable for Foo@GOTPCREL]
add rcx, 16
mov qword ptr [rax], rcx
mov rdi, rax
call Foo::foo()@PLT
xor eax, eax
pop rcx
ret
我并没有真正详细地理解它,但我清楚地阅读了.为什么它甚至在那里?vtable
我本来希望上面的 TU 的组装与我删除关键字时得到的组装相同:virtual
main: # @main
push rax
mov edi, 1
call operator new(unsigned long)@PLT
mov rdi, rax
call Foo::foo()@PLT
xor eax, eax
pop rcx
ret
从另一个答案中,我读到
A* a = new B; a->func();
在这种情况下,编译器可以确定指向对象,从而调用正确的版本,而无需动态调度。[...]当然,编译器是否进行相应的分析取决于其各自的实现。
a
B
func()
答案是否只是 Clang 没有进行分析,从而推断出不需要运行时调度?
还是我错过了什么?也许我只是完全误解了大会?
答:
这条线
mov rcx, qword ptr [rip + vtable for Foo@GOTPCREL]
初始化 vtable 指针。
vtable 机制不用于调用 ,但是每个具有 vtable 的对象都需要初始化其 vtable 指针。如果不初始化对象的 vtable 指针,可能会中断(例如,它可以尝试和)。Foo::foo
Foo
Foo::foo()
dynamic_cast
this
如果你给出一个可内联的主体,编译器可能会(也可能不会)优化整个对象,这取决于主体中的具体内容。但是,编译器不太可能允许存在具有未初始化的 vtable 指针的对象。实在是太麻烦了,不值得费力去处理这个边缘案件。Foo::foo
Foo
您显示的程序集中的 的调用已解析为对 的直接调用。foo
Foo::foo
对 vtable 的引用是必需的,因为指向它的指针需要存储在对象中。的实现可能依赖于它,例如通过使用 、 ,甚至只是递归调用 on 。Foo
Foo::foo
dynamic_cast
typeid
foo
this
评论
foo
main
return 0;
评论
// stuff
foo()
没有身体,真正的绑定留到链接状态。添加正文:godbolt.org/z/eosx6nG3MFoo::foo()
vtable
*foo
foo()
foo()
typeid(*this)