虚函数实现多态的核心在于编译器为含虚函数的类生成虚函数表(vtable)并维护vptr,运行时通过vptr动态绑定函数地址;空类加virtual函数后sizeof变为指针宽度(如x64下8字节),可初步验证虚表存在。

虚函数调用为什么能实现多态
核心在于:编译器为含虚函数的类生成虚函数表(vtable),每个对象头存储指向该表的指针(vptr)。运行时通过 vptr 找到正确的函数地址,而非编译期绑定。
注意:只有被声明为 virtual 的成员函数才进虚表;普通重载、静态成员函数、构造函数不参与多态;析构函数建议显式加 virtual,否则 delete 基类指针可能漏掉派生类清理逻辑。
如何验证一个类有没有虚函数表
最直接的办法是看对象大小是否“异常”——比如空类通常占 1 字节,但若加了 virtual 函数,sizeof 会变成指针宽度(x64 下为 8 字节):
struct A { virtual void f() {} };
struct B { void f() {} };
static_assert(sizeof(A) == 8); // 通常成立
static_assert(sizeof(B) == 1); // 通常成立
更可靠的方式是用调试器观察对象内存布局(如 VS 的内存窗口),或借助编译器扩展(GCC 的 -fdump-class-hierarchy)输出虚表结构。
立即学习“C++免费学习笔记(深入)”;
虚函数表在继承中的变化规则
单继承下,派生类虚表通常复用基类部分,并在末尾追加新虚函数;若重写了基类虚函数,则对应槽位被替换为派生类版本地址。多重继承会更复杂:一般只让第一个基类的虚表作为主虚表,其余基类虚表单独存放,且派生类对象内存中可能出现多个 vptr。
- 派生类未重写虚函数 → 虚表中该槽仍指向基类实现
- 派生类新增虚函数 → 新增槽位,排在虚表末尾
- 派生类重写虚函数 → 对应槽位地址更新为派生类函数入口
- 虚函数被
override但签名不匹配 → 编译报错,不会进虚表
哪些操作会破坏虚函数调用的正确性
本质是让 vptr 指向错误的虚表,或让对象处于未定义状态:
- 用
memcpy或memset拷贝/清零含虚函数的对象 →vptr可能被覆盖或失效 - 把派生类对象强制 reinterpret_cast 成基类引用/指针以外的类型 → 绕过虚表查找机制
- 在构造函数或析构函数里调用虚函数 → 此时
vptr指向当前正在构造/析构的类的虚表,不会动态绑定到最终派生类 - 返回局部对象的引用或指针 → 对象已销毁,
vptr指向野内存
虚表本身是只读数据段内容,但误操作对象内存比搞错虚表结构更容易出问题——多数崩溃不是虚表不存在,而是 vptr 不再可信。











