C++虚拟指针及其机制

问题描述 投票:0回答:2

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

我理解在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::vptr
(命名),然后是
Derived::vtable
,并且
Derived::foo()
遵循与
p_base->foo()
相同的路径。但是
Derived::vptr -> Derived::vtable -> Derived::foo()
如何找到
p_base->foo()
,即使它的静态类型是
Derived::vptr
?是什么阻止
Base*
找到
p_base

非常感谢

c++ vtable
2个回答
2
投票

vtable 是实现定义的,因此根据标准,任何有效的操作都是可以的。

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

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

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

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

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

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


0
投票

© www.soinside.com 2019 - 2024. All rights reserved.