信息发布→ 登录 注册 退出

C++中的虚函数(virtual)是如何实现的?(虚函数表和虚表指针)

发布时间:2026-01-09

点击量:
虚函数调用需查表因编译期无法确定具体调用版本,故运行时通过vptr和vtable实现多态;每个含虚函数的类有静态vtable,对象头含隐式vptr指向对应vtable,vptr不参与sizeof且不可修改。

虚函数调用为什么需要查表?

因为编译期无法确定 ptr->func() 到底该调用哪个版本的函数——ptr 可能指向 BaseDerived1Derived2 对象。C++ 选择在运行时通过间接跳转实现多态,而不是生成所有可能的分支(那会爆炸式膨胀代码)。这个“间接跳转”的载体就是虚函数表(vtable)和每个对象头里的虚表指针(vptr)。

vptr 和 vtable 在内存里长什么样?

每个含虚函数的类(包括派生类)在编译期生成一张静态的 vtable,里面按声明顺序存放函数指针;每个该类的对象在内存布局最前面隐式插入一个 vptr,初始化时指向其所属类的 vtable。注意:vptr 不是成员变量,不参与 sizeof 计算(但影响对象实际大小),也不可被取地址或修改。

struct Base {
    virtual void f() { }
    virtual void g() { }
};
struct Derived : Base {
    void f() override { }  // 覆盖 Base::f
    void h() { }           // 新增非虚函数
};
// 编译后:
// Base::vtable = { &Base::f, &Base::g }
// Derived::vtable = { &Derived::f, &Base::g }  ← g 未重写,复用基类地址
// new Base → 内存:[vptr→Base::vtable] + 成员数据
// new Derived → 内存:[vptr→Derived::vtable] + Base 成员 + Derived 成员

多重继承下 vptr 怎么处理?

一个类若从多个含虚函数的基类继承,它会为每个这样的基类子对象维护独立的 vptr。例如 class D : public A, public B(A/B 都有虚函数),则 D 对象内存中会有两个 vptr:一个紧贴 A 子对象起始处,指向 A 的 vtable;另一个在 B 子对象起始处,指向 B 的 vtable。强制转型(如 static_cast(d_ptr))本质就是调整指针值,使其对齐到 B 子对象的起始地址(即第二个 vptr 所在位置)。

  • 单继承:通常只有一个 vptr,位于对象开头
  • 多重继承:可能有多个 vptr,位置取决于基类声明顺序和 ABI(如 Itanium C++ ABI)
  • 虚继承:还会引入额外的偏移量字段,用于运行时修正 this 指针,vtable 条目也可能带 offset 数据

哪些操作会破坏 vptr 的有效性?

vptr 是构造/析构过程中由编译器自动管理的,手动干预极易出错:

  • memcpy 复制含虚函数的对象 → 只复制了 vptr 值,但新对象的生命周期未触发构造函数,vptr 可能指向已销毁的 vtable 或不匹配的类
  • 把派生类对象赋给基类数组(切片)→ 基类数组元素只有基类大小,vptr 被截断或覆盖,后续虚调用 UB
  • 在构造函数里调用虚函数 → 此时 vptr 指向当前正在构造的类的 vtable,不会动态绑定到最终派生类(即使对象是派生类实例)

虚函数机制本身很轻量,但依赖严格的对象生命周期和内存布局。一旦绕过构造/析构流程,vptr 就成了悬空指针。

标签:# 切片  # 能有  # 第二个  # 也可  # 还会  # 隐式  # 都有  # 跳转  # 多个  # 派生类  # this  # 对象  # c++  # 空指针  # 多重继承  # public  # class  # 虚函数  # 继承  # 指针  # 构造函数  # 成员变量  # 多态  # 为什么  
在线客服
服务热线

服务热线

4008888355

微信咨询
二维码
返回顶部
×二维码

截屏,微信识别二维码

打开微信

微信号已复制,请打开微信添加咨询详情!