vtable是C++运行时多态的核心机制,由编译器为含虚函数的类生成只读函数指针数组,按声明顺序存储虚函数地址;对象首部隐式包含vptr指向对应vtable,调用时通过vptr+vtable索引动态分派。

vtable(虚函数表)是 C++ 实现运行时多态的核心底层机制。它不是语言标准强制规定的结构,但所有主流编译器(如 MSVC、GCC、Clang)都采用类似方案:每个含虚函数的类,在编译期生成一张只读的函数指针数组,存放该类所有虚函数的实际地址——这就是 vtable。
vtable 是怎么生成和组织的?
编译器为每个含虚函数的类单独生成一张 vtable,按虚函数在类中声明的顺序依次填入地址:
- 基类的 vtable 包含其自身所有虚函数地址,索引从 0 开始连续排列;
- 派生类继承基类 vtable 布局,重写的虚函数会覆盖对应索引位置的地址;
- 派生类新增的虚函数则追加到表尾,不打乱原有索引顺序;
- vtable 存储在程序的
.rodata段,所有同类对象共享同一份,只读且全局唯一。
vptr 怎么把对象和 vtable 连起来?
每个含虚函数的类的对象,内存布局开头隐式插入一个指针成员,叫 vptr(虚表指针),大小为 4 字节(x86)或 8 字节(x64):
- vptr 在对象构造时由编译器自动初始化,指向其静态类型对应的 vtable;
- 例如
Derived d;的 vptr 指向Derived类的 vtable,哪怕用Base* ptr = &d;持有,vptr 仍不变; - 该指针始终位于对象内存首地址,可通过
*(void**)(&obj)直接取到(调试/教学用途,生产代码勿用)。
调用虚函数时发生了什么?
当通过基类指针或引用调用虚函数(如 ptr->func()),实际执行分四步:
立即学习“C++免费学习笔记(深入)”;
- 从
ptr所指对象的首地址读出 vptr; - 通过 vptr 找到对应类的 vtable 起始地址;
- 根据
func在基类中声明的顺序,查其在 vtable 中的固定偏移(比如第 0 项); - 取出该位置存储的函数地址,跳转执行——此时若对象是派生类实例,取到的就是派生类重写后的地址。
为什么不用每个对象存全部函数指针?
直接在对象里放 10 个函数指针,1000 个对象就浪费数 KB 内存。vtable 方案把重复数据上提至类级别,每个对象只增一个 vptr,空间开销极小,且继承时只需复用+局部覆盖,逻辑清晰、高效可靠。










