虚函数调用通过运行时vtable间接跳转实现多态;编译器为每个含virtual函数的类生成存于.rodata段的静态vtable,对象头含vptr指向它;vptr占8字节导致sizeof增大,且vtable修改属未定义行为。

虚函数调用不是靠编译期绑定,而是运行时通过虚函数表(vtable)间接跳转——这是实现多态的底层基础。没有 vtable,virtual 就只是个语法标记。
虚函数表(vtable)是谁生成的?存在哪?
每个含 virtual 函数的类(或其子类),编译器会在编译期为它生成一张静态的函数指针数组,即 vtable;该表通常存放在只读数据段(.rodata),全局唯一,不随对象数量增加而复制。
- 类本身不“拥有”vtable,但每个对象头(通常在最前面)会隐式插入一个
vptr指针,指向所属类的 vtable - 派生类若重写虚函数,其 vtable 中对应槽位会被替换成派生类版本的函数地址;若新增虚函数,则在表末尾追加
- 多重继承下,对象内存中可能出现多个
vptr(分别对应不同基类子对象),vtable 也相应分片
虚函数调用如何查表?为什么不能 inline?
形如 ptr->func() 的调用,若 func 是虚函数,实际汇编是三步:取 vptr → 查 vtable 对应偏移 → 跳转到函数地址。这个过程无法在编译期确定目标地址,所以编译器默认禁用 inline。
- 即使函数体极短(比如只 return 1),只要声明为
virtual,就大概率不会被内联,除非开启 LTO + 全局分析且能证明调用目标唯一 - 直接通过对象而非指针/引用调用(如
obj.func())时,若类型已知且无继承关系,部分编译器可能 devirtualize(去虚化),此时可能 inline -
final或override关键字本身不改变查表行为,但可辅助编译器做更激进的优化判断
sizeof(class) 为什么常比成员总和大?
因为含虚函数的类对象必须容纳至少一个 vptr,它占 8 字节(64 位系统),且受对齐影响可能插在结构体开头或中间。
立即学习“C++免费学习笔记(深入)”;
- 空类
struct A { virtual ~A(); };的sizeof(A)是 8,不是 1 - 若类已有成员且自然对齐到 8 字节边界,
vptr可能“免费”插入,不额外增加大小 - 多重继承中,第二个及以上基类的
vptr会进一步增加对象体积,且可能破坏内存连续性
vtable 是编译器实现细节,标准未规定布局,但主流编译器(GCC、Clang、MSVC)都采用类似设计。真正容易被忽略的是:vtable 指针修改是未定义行为,RTTI 信息(如 type_info)和异常处理栈展开也依赖同一套虚表机制——它们不是孤立存在。








