虚函数表(VTable)是C++实现运行时多态的核心机制,每个含虚函数的类有静态函数指针表,按声明顺序存虚函数地址;每个对象头部隐式存储vptr指向所属类VTable;虚函数调用编译为“读vptr→查表→跳转”,支持单/多重/虚继承下的动态绑定。

虚函数表(VTable)是C++实现运行时多态的核心机制,它让基类指针或引用能调用派生类的重写函数——不是靠编译期决定,而是靠对象内存中隐藏的函数地址跳转来完成。
每个含虚函数的类都有一个VTable
编译器为每个定义了虚函数(或继承了虚函数)的类生成一张静态的函数指针表。这张表里按声明顺序存放着该类所有虚函数的实际地址(即最终要执行的函数入口)。比如:
-
Base 类有
virtual void foo()和virtual void bar(),它的 VTable 就有两个指针,分别指向 Base::foo 和 Base::bar 的代码段; -
Derived 继承 Base 并重写了
foo(),那么 Derived 的 VTable 中第一个指针就换成 Derived::foo 的地址,第二个仍为 Base::bar(除非也被重写); - 如果 Derived 新增了虚函数
virtual void baz(),这个函数地址会追加到 VTable 末尾。
每个对象头部隐式存储一个vptr(虚函数表指针)
只要类有虚函数,编译器就会在该类的每个对象最开始的位置(通常是前8字节,在64位系统上)插入一个指针 vptr,它指向所属类的 VTable。这个过程对程序员完全透明:
- 创建
Base b;→ b 的内存开头是 &Base::vtable; - 创建
Derived d;→ d 的内存开头是 &Derived::vtable; - 当用
Base* p = new Derived;时,p 指向的是 d 的起始地址,所以 p->vptr 实际指向的是 Derived 的 VTable。
虚函数调用被编译成“查表+跳转”指令
像 p->foo(); 这样的调用,编译后不是直接 call 地址,而是三步操作:
立即学习“C++免费学习笔记(深入)”;
- 从 p 所指对象的首地址读出 vptr;
- 根据 foo 在虚函数声明顺序中的索引(比如第0个),访问 vptr[0] 得到函数地址;
- 间接 call 这个地址。
这就是为什么虚函数调用比普通函数慢一点:多了一次内存读取和一次间接跳转。但现代CPU分支预测和缓存通常能缓解这部分开销。
多重继承和虚继承会让vptr变复杂
单继承下一般只有一个 vptr;但多重继承时,派生类对象可能包含多个子对象布局,每个“基类子对象”区域都可能有自己的 vptr:
-
class D : public A, public B→ D 对象内存中可能有 A 部分的 vptr 和 B 部分的 vptr; - 虚继承引入虚基类子对象,还会额外增加偏移调整逻辑(有时通过 thunk 中转函数修正 this 指针);
- 这些细节由编译器自动生成,开发者通常只需知道“多态仍能正确工作”,不必手动管理 vptr。
基本上就这些。VTable 是静态的、类级别的;vptr 是动态的、对象级别的。两者配合,让 C++ 在零运行时反射的前提下,实现了高效的动态绑定。不复杂但容易忽略——它藏在对象内存布局最前面,默默支撑着 every virtual call。











