MST

星途 面试题库

面试题:C++中虚函数表的结构与访问机制

请详细描述C++虚函数表在内存中的结构,以及当通过对象指针调用虚函数时,程序是如何通过虚函数表找到对应的函数地址并执行的。假设存在一个包含虚函数的基类和派生类,给出简单代码示例辅助说明。
10.7万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

C++虚函数表在内存中的结构

  1. 单继承无虚函数覆盖情况
    • 每个包含虚函数的类都有一个虚函数表(vtable)。虚函数表是一个存储虚函数地址的数组。
    • 当类中有虚函数时,该类的每个对象都有一个隐藏的指针(vptr),这个指针指向该类的虚函数表。
    • 对于基类,虚函数表中按照声明顺序依次存储虚函数的地址。
  2. 单继承有虚函数覆盖情况
    • 派生类继承基类的虚函数表,并对覆盖的虚函数在虚函数表中替换为自己的函数地址。未覆盖的虚函数地址仍保持基类中的顺序。
  3. 多重继承情况
    • 派生类可能会有多个虚函数表,每个虚函数表对应一个基类。这是因为每个基类的虚函数布局需要保持独立。
    • 派生类对象的内存布局中,会有多个vptr指针,分别指向不同基类对应的虚函数表。
  4. 菱形继承情况
    • 为了解决菱形继承中的数据冗余和二义性问题,引入虚继承。虚继承下,虚函数表结构会更复杂,通常会有一个虚基类表指针(vbptr)和虚基类表,虚函数表的布局也会根据虚继承的规则进行调整。

通过对象指针调用虚函数的过程

  1. 当通过对象指针调用虚函数时,首先根据对象的地址找到隐藏的vptr指针。
  2. 通过vptr指针找到对应的虚函数表。
  3. 在虚函数表中,根据虚函数在表中的索引找到对应的函数地址。索引是在编译期确定的,与虚函数声明顺序相关。
  4. 获得函数地址后,程序跳转到该地址执行虚函数的代码。

简单代码示例

#include <iostream>

class Base {
public:
    virtual void func() {
        std::cout << "Base::func()" << std::endl;
    }
};

class Derived : public Base {
public:
    void func() override {
        std::cout << "Derived::func()" << std::endl;
    }
};

int main() {
    Base* basePtr = new Derived();
    basePtr->func();// 通过对象指针调用虚函数
    delete basePtr;
    return 0;
}

在上述代码中:

  1. Base类包含一个虚函数funcBase类对象有一个vptr指针指向其虚函数表,虚函数表中存储Base::func的地址。
  2. Derived类继承自Base类并覆盖了func函数,Derived类对象的vptr指针指向Derived类的虚函数表,该虚函数表中func函数的地址被替换为Derived::func的地址。
  3. main函数中,basePtr虽然是Base*类型,但实际指向Derived对象。当调用basePtr->func()时,通过basePtr找到Derived对象的vptr,再通过vptr找到Derived类的虚函数表,从而执行Derived::func函数。