C++虚指针及其机制
C++ Virtual Pointer and its mechanism
我提出这个问题是因为在阅读了很多帖子和答案之后我仍然没有得到我的答案。
如果是这样,请将其标记为重复。
我了解到在C++中,虚函数是通过虚指针和virtual实现的table。
但是我不确定 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
找到 Base::vptr
?
非常感谢
我真的认为相关的答案有你需要知道的一切,但可能还有点遗漏。
vtable 是实现定义的,因此根据标准,任何有效的东西都是可以的。
一种方法是让实际的 vtable 成为 const static,并且在每个构造函数中更新单个指针以指向每个新的 class vtable .这有双重间接惩罚,但一个好处是恶意软件不可能覆盖函数指针。
另一种方法是使用 table,也就是指针数组。虚拟指针table:vtable。在这个方法中,每组指针都是在每个构造函数中设置的。或者至少看起来是这样:优化器可以做一些奇怪的事情。
多重继承、多重虚拟继承等会使事情变得极其复杂。甚至可以嵌套 vtables:tables 指向其他 tables!
然后我们当然得到整个程序的优化。 Unix 上的 LTO。 MSVC中的LTCG等。优化器可以遍历程序,如果可以确定虚拟调用只能转到一个目标函数,则将调用替换为非虚拟直接调用。然后重新运行内联过程。配置文件定向优化甚至可以采用可变虚函数并确定它在 80% 的时间内调用 class A。然后它可能总是调用 A,并进行线外检查以查看它实际上是 B 还是其他东西。
但在您设计的简单情况下,class Base 有一个 vtable 和一个指向 foo 的函数指针。当 Base() 构造函数运行时,它被设置为 Base::foo。 Derived() 运行后它被设置为 Derived::foo()
如果没有复杂的虚拟继承或多重继承,基础(在你的例子中是 Base)class 总是在结构的前面。所以指向 Base 的指针总是指向前面。 vtable 在哪里。指向 Derived 的指针也指向前面。两个 classes 使用相同的 vtable 并调用那里设置的函数。
我提出这个问题是因为在阅读了很多帖子和答案之后我仍然没有得到我的答案。 如果是这样,请将其标记为重复。
我了解到在C++中,虚函数是通过虚指针和virtual实现的table。
但是我不确定 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
找到 Base::vptr
?
非常感谢
我真的认为相关的答案有你需要知道的一切,但可能还有点遗漏。
vtable 是实现定义的,因此根据标准,任何有效的东西都是可以的。
一种方法是让实际的 vtable 成为 const static,并且在每个构造函数中更新单个指针以指向每个新的 class vtable .这有双重间接惩罚,但一个好处是恶意软件不可能覆盖函数指针。
另一种方法是使用 table,也就是指针数组。虚拟指针table:vtable。在这个方法中,每组指针都是在每个构造函数中设置的。或者至少看起来是这样:优化器可以做一些奇怪的事情。
多重继承、多重虚拟继承等会使事情变得极其复杂。甚至可以嵌套 vtables:tables 指向其他 tables!
然后我们当然得到整个程序的优化。 Unix 上的 LTO。 MSVC中的LTCG等。优化器可以遍历程序,如果可以确定虚拟调用只能转到一个目标函数,则将调用替换为非虚拟直接调用。然后重新运行内联过程。配置文件定向优化甚至可以采用可变虚函数并确定它在 80% 的时间内调用 class A。然后它可能总是调用 A,并进行线外检查以查看它实际上是 B 还是其他东西。
但在您设计的简单情况下,class Base 有一个 vtable 和一个指向 foo 的函数指针。当 Base() 构造函数运行时,它被设置为 Base::foo。 Derived() 运行后它被设置为 Derived::foo()
如果没有复杂的虚拟继承或多重继承,基础(在你的例子中是 Base)class 总是在结构的前面。所以指向 Base 的指针总是指向前面。 vtable 在哪里。指向 Derived 的指针也指向前面。两个 classes 使用相同的 vtable 并调用那里设置的函数。