我提出这一点是因为在阅读了许多帖子和答案后,我仍然没有得到我的答案。如果是这样的话,请标记为副本。
在C中,虚函数是通过虚指针和虚表实现的。
然而,我不确定C编译器如何在运行时破译哪个虚拟指针?
在下面简单的情况下:
class Base {
public:
virtual foo() {}
}
class Derived: public Base {
public:
foo() override {}
}
int main() {
D* p_derived = new Derived();
B* p_base = p_derived;
p_derived->foo();
p_base->foo();
}
我知道p_derived->foo()
将为Derived::vtable
(命名)查找Derived::vptr
本身,然后Derived::foo()
,p_base->foo()
遵循与Derived::vptr -> Derived::vtable -> Derived::foo()
相同的路径。但是p_base->foo()
如何找到Derived::vptr
,即使它的静态类型是Base*
?是什么阻止了p_base
查找Base::vptr
?
非常感谢
2条答案
按热度按时间nhjlsmyf1#
我真的认为相关的答案有你需要知道的一切,但可能有一点缺失。
vtable是实现定义的,所以根据标准,任何工作都是可以的。
一种方法是让实际的vtable是const static,并且在每个构造函数中,单个指针被更新为指向每个新的类vtable。这有双重间接惩罚,但一个好处是恶意软件不可能覆盖函数指针。
另一种方法是有一个表,又名数组,指针。虚拟指针表:vtable。在这个方法中,在每个构造函数中设置每组指针。或者至少看起来是这样的:优化器可以做奇怪的事情。
多重继承、多重虚拟继承等会使事情变得非常复杂。甚至可以有嵌套的vtables:table指向其他table!
当然,我们会得到整个程序的优化。Unix上的LTO。MSVC中的LTCG等优化器可以遍历程序,如果它可以确定虚拟调用只能转到一个目标函数,那么它将用非虚拟直接调用替换该调用。然后重新运行内联传递。配置文件定向优化甚至可以采用一个变量虚函数,并确定它在80%的时间内正在调用类A。然后它可能总是调用A,并进行离线检查以确定它实际上是B还是其他。
但在您所设计的简单情况下,类Base有一个vtable,其中有一个指向foo的函数指针。当Base()构造函数运行时,它被设置为Base::foo。在Derived()运行之后,它被设置为Derived::foo()
如果没有复杂的虚继承或多重继承,基类(在本例中为Base)始终位于结构的前面。所以指向Base的指针总是指向前面。vtable在哪里指向Derived的指针也指向前面。这两个类使用相同的vtable,并调用其中设置的任何函数。
q3qa4bjr2#
p_base只是一个指针而不是示例。我认为p_base可以访问的vptr是p_derived的vptr。