虚函数调用需查表因编译期无法确定具体调用版本,故运行时通过vptr和vtable实现多态;每个含虚函数的类有静态vtable,对象头含隐式vptr指向对应vtable,vptr不参与sizeof且不可修改。

虚函数调用为什么需要查表?
因为编译期无法确定 ptr->func() 到底该调用哪个版本的函数——ptr 可能指向 Base、Derived1 或 Derived2 对象。C++ 选择在运行时通过间接跳转实现多态,而不是生成所有可能的分支(那会爆炸式膨胀代码)。这个“间接跳转”的载体就是虚函数表(vtable)和每个对象头里的虚表指针(vptr)。
vptr 和 vtable 在内存里长什么样?
每个含虚函数的类(包括派生类)在编译期生成一张静态的 vtable,里面按声明顺序存放函数指针;每个该类的对象在内存布局最前面隐式插入一个 vptr,初始化时指向其所属类的 vtable。注意:vptr 不是成员变量,不参与 sizeof 计算(但影响对象实际大小),也不可被取地址或修改。
struct Base {
virtual void f() { }
virtual void g() { }
};
struct Derived : Base {
void f() override { } // 覆盖 Base::f
void h() { } // 新增非虚函数
};
// 编译后:
// Base::vtable = { &Base::f, &Base::g }
// Derived::vtable = { &Derived::f, &Base::g } ← g 未重写,复用基类地址
// new Base → 内存:[vptr→Base::vtable] + 成员数据
// new Derived → 内存:[vptr→Derived::vtable] + Base 成员 + Derived 成员
多重继承下 vptr 怎么处理?
一个类若从多个含虚函数的基类继承,它会为每个这样的基类子对象维护独立的 vptr。例如 class D : public A, public B(A/B 都有虚函数),则 D 对象内存中会有两个 vptr:一个紧贴 A 子对象起始处,指向 A 的 vtable;另一个在 B 子对象起始处,指向 B 的 vtable。强制转型(如 static_cast(d_ptr))本质就是调整指针值,使其对齐到 B 子对象的起始地址(即第二个 vptr 所在位置)。
- 单继承:通常只有一个
vptr,位于对象开头 - 多重继承:可能有多个
vptr,位置取决于基类声明顺序和 ABI(如 Itanium C++ ABI) - 虚继承:还会引入额外的偏移量字段,用于运行时修正 this 指针,vtable 条目也可能带 offset 数据
哪些操作会破坏 vptr 的有效性?
vptr 是构造/析构过程中由编译器自动管理的,手动干预极易出错:
- 用
memcpy复制含虚函数的对象 → 只复制了vptr值,但新对象的生命周期未触发构造函数,vptr可能指向已销毁的 vtable 或不匹配的类 - 把派生类对象赋给基类数组(切片)→ 基类数组元素只有基类大小,
vptr被截断或覆盖,后续虚调用 UB - 在构造函数里调用虚函数 → 此时
vptr指向当前正在构造的类的 vtable,不会动态绑定到最终派生类(即使对象是派生类实例)
虚函数机制本身很轻量,但依赖严格的对象生命周期和内存布局。一旦绕过构造/析构流程,vptr 就成了悬空指针。










