虚函数多态依赖vptr和vtable运行时查表实现:对象首部存vptr,调用时通过vtable偏移跳转函数地址;仅指针/引用调用虚函数才动态绑定,对象直接调用、切片、构造析构中调用均退化为静态绑定。

虚函数调用为什么能实现多态
虚函数的多态能力不来自语法糖,而依赖编译器在类布局中插入的 vptr(虚函数表指针)和运行时通过 vtable 查表跳转。只要基类声明了 virtual 函数,哪怕没定义,编译器也会为该类生成虚函数表,并在每个对象内存布局开头隐式插入一个 vptr 指向它。
当通过基类指针或引用调用虚函数时,实际执行的是:
1. 从对象首地址读取 vptr
2. 根据函数在虚表中的偏移(编译期确定)查到函数地址
3. 间接跳转执行 —— 这个过程发生在运行时,所以能根据实际对象类型调用对应版本
虚函数表(vtable)长什么样?怎么验证
每个含虚函数的类(包括派生类)有唯一一份静态 vtable,里面存的是函数指针(不是函数名),顺序按虚函数声明顺序排列,析构函数如果虚也会占一个槽位。你可以用 GDB 或 Clang 的 -fdump-class-hierarchy 看到真实布局:
class Base {
public:
virtual void f() { }
virtual void g() { }
};
对应 vtable 类似:vtable for Base:
[0]: Base::f
[1]: Base::g
若 Derived : public Base 重写了 f(),它的 vtable 就是:
[0]: Derived::f ← 覆盖
[1]: Base::g ← 继承未改
注意:sizeof(Base) 在 64 位系统通常是 8(仅含一个 vptr),别误以为是 0。
哪些情况会破坏虚函数多态行为
多态只在“通过指针或引用、且调用的是虚函数”时生效。以下常见写法会静默退化为静态绑定:
立即学习“C++免费学习笔记(深入)”;
- 直接用对象调用:
Base b; b.f();→ 编译期绑定,哪怕f是虚函数 - 派生类对象赋值给基类对象(非指针/引用):
Base b = Derived();→ 发生切片,vptr被替换为Base的,多态丢失 - 构造/析构函数中调用虚函数:此时
vptr指向当前正在构造/析构的类的 vtable,不会调用派生类重写的版本(C++ 标准规定) - 虚函数内联失败但被强制
inline:编译器可能拒绝内联虚函数,但若强行加inline关键字,不会报错,只是无效;真正影响的是能否 devirtualize(如 LTO 下的去虚拟化优化)
面试常问的底层细节陷阱
面试官喜欢抠边界情形,比如:
– 多重继承下 vptr 不止一个(如菱形继承中虚继承会引入额外 vptr),对象内存布局变复杂,static_cast 和 dynamic_cast 行为差异明显;
– 纯虚函数在 vtable 中填的是哑元(如 GCC 填 __cxa_pure_virtual),调用会 abort;
– 虚函数表本身不可修改(只读段),但某些嵌入式环境或热补丁场景会 patch vtable,属于未定义行为;
– std::function 或 lambda 捕获虚函数调用,本质仍是通过对象指针查表,不改变多态机制。
真正容易出错的是把“有虚函数”等同于“自动多态”,而忽略调用方式和对象生命周期这两个硬性前提。











