虚函数表是 C++ 编译器为实现动态绑定(运行时多态)而生成的内部数据结构。每个包含虚函数的类(或继承自含虚函数类的派生类)都拥有一张静态的虚函数表,表中按声明顺序存放虚函数的地址。
类对象内存模型:
+----------------+
| vptr (隐藏指针) | --> 指向该类的虚函数表
+----------------+
| 非静态数据成员 |
+----------------+
虚函数表(存储在只读数据段):
+----------------+
| &Base::vfunc1 |
| &Base::vfunc2 |
| &Base::vfunc3 |
+----------------+
vptr 在对象构造时由编译器自动初始化,指向所属类的虚表。单继承下,派生类对象含一个 vptr;多继承/虚继承下可能含多个 vptr。
__cxa_pure_virtual(运行时调用会终止程序)。delete base_ptr 正确调用派生析构。Base *ptr = new Derived();
ptr->vfunc(); // 编译器生成类似:
// 1. 取 ptr->vptr
// 2. 以固定偏移索引虚表,取函数地址
// 3. 间接跳转调用
这就是“间接调用”带来的微小开销(一次内存读取 + 间接跳转),现代 CPU 分支预测可大幅缓解。
struct A { virtual void f(); };
struct B { virtual void g(); };
struct C : A, B { virtual void h(); };
// C 对象包含两个 vptr:
// [A 子对象 vptr] -> A 的虚表 + C 覆盖/新增
// [B 子对象 vptr] -> B 的虚表 + C 覆盖/新增
// this 指针调整:B::g 调用时 this 需偏移到 B 子对象起始
编译器在虚表中额外存储 thunk(调整 this 指针的小代码桩)或偏移量。
虚基类子对象在派生类中仅有一份,虚表项存储 vbase_offset 指示虚基类相对位置,运行时通过 vptr 间接查找。
虚表首项(或紧邻)常存放指向 type_info 对象的指针,支持 dynamic_cast、typeid 运行时类型识别。
vptr 尚指向当前类虚表,派生部分未构造,调用基类版本。p /x *(obj->vptr) 查看虚表地址,p *(void**)obj 取 vptr。-fno-rtti 可禁用 RTTI 节省空间。gcc -fdump-class-hierarchy、clang -cc1 -emit-llvm -o - 查看 IR。std::variant 访问者模式。final 允许编译器去虚化(devirtualization)。虚函数表是 C++ 多态的基石,理解其布局、构建规则、多重/虚继承扩展,有助于排查切片问题、优化性能、实现序列化/插件系统等高级场景。记住:虚表属于类,vptr 属于对象,动态绑定 = vptr → 虚表 → 函数地址 → 间接调用。