0

我提出这个是因为在阅读了许多帖子和答案后,我仍然没有得到答案。如果是这样,请将此标记为重复。

我理解在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()将寻找(命名) thenDerived::vptr本身,并遵循与. 但是,即使它是静态类型,它又是如何找到的呢?是什么阻止了寻找呢?Derived::vtableDerived::foo()p_base->foo()Derived::vptr -> Derived::vtable -> Derived::foo()p_base->foo()Derived::vptr Base*p_baseBase::vptr

非常感谢

4

1 回答 1

2

我真的认为相关的答案有你需要知道的一切,但可能有点缺失。

vtable 是实现定义的,所以根据标准,任何工作都可以。

一种方法是让实际的 vtable 为 const static,并且在每个构造函数中更新单个指针以指向每个新的类 vtable。这有双重间接惩罚,但一个好处是恶意软件不可能覆盖函数指针。

另一种方法是有一个指针表,也就是数组。虚拟指针表:vtable。在此方法中,每组指针在每个构造函数期间设置。或者至少看起来是这样的:优化器可以做一些奇怪的事情。

多重继承、多重虚拟继承等会使事情变得非常复杂。甚至可能有嵌套的 vtables:指向其他表的表!

然后当然我们得到整个程序优化。Unix 上的 LTO。MSVC中的LTCG等。优化器可以通过程序,如果它可以确定虚拟调用只能到达一个目标函数,那么它将调用替换为非虚拟直接调用。然后重新运行内联传递。配置文件定向优化甚至可以采用可变虚函数并确定它在 80% 的时间调用 A 类。然后它可能总是调用 A,并进行离线检查以查看它实际上是 B 还是其他东西。

但是在您列出的简单案例中,Base 类有一个 vtable,其中有一个指向 foo 的函数指针。当 Base() 构造函数运行时,它被设置为 Base::foo。Derived() 运行后设置为 Derived::foo()

如果没有复杂的虚拟继承或多重继承,则基类(在您的情况下为 Base)始终位于结构的前面。所以指向 Base 的指针总是指向前面。虚表在哪里。指向 Derived 的指针也指向前面。两个类都使用相同的 vtable 并调用那里设置的任何函数。

于 2020-03-28T04:48:32.060 回答