运行时编译器如何确定vtable中与被调用虚函数对应的条目?
核心结论
你首先要纠正一个常见误区:虚函数的表项定位根本不需要运行时做查找,更不会有编译器在运行时参与逻辑,所有索引偏移都是编译阶段就确定好的,运行时只是按固定偏移取地址执行而已。
具体执行流程分两个阶段:
1. 编译阶段:确定调用的固定偏移
编译器处理bptr->Print2()这行代码时,只会看bptr的静态类型Base1*:
- 检索
Base1类的虚函数声明顺序,Print1是第0个、Print2是第1个、Print3是第2个,直接就把这行调用翻译成「取对象指向的虚表的第1个偏移位置的函数地址,执行调用」的逻辑,完全不关心bptr实际指向什么类型的对象。 - 你示例的代码编译后的伪逻辑等价于:
// 编译期就固定了取虚表索引为1的条目 void** vtable = *(void***)(bptr); // 取对象首地址存储的虚表指针 using Func = void(*)(); (*(Func)vtable[1])(); // 调用对应地址的函数
2. 派生类虚表生成规则
C++标准要求派生类的虚表和其公共基类的虚表,相同虚函数的索引位置必须完全一致:
- 派生类构建虚表时,会完全对齐基类的虚函数顺序,如果你重写了某个虚函数,就把对应索引位置的地址替换为派生类自己的实现,没重写就保留基类的函数地址。
- 你示例里
Derived类重写了所有3个虚函数,所以它的虚表索引1位置存的本来就是Derived::Print2的地址。
3. 运行时执行
运行时bptr指向Derived类对象,对象首地址存的是Derived类的虚表指针,直接按照编译期固定的偏移1去取对应地址执行,就自然调用到了Derived::Print2,全程没有任何查找、匹配的操作,性能开销只比普通函数调用多两次内存读取。
这种设计也是C++「零开销抽象」原则的体现,避免了运行时遍历匹配的额外开销。
内容的提问来源于stack exchange,提问作者Roger
相关产品推荐
相关产品推荐

