虚函数表(vtable)
每个包含虚函数(或继承自含虚函数的类)的类,都会在编译阶段生成一个唯一的虚函数表。它本质是一个函数指针数组,但并非仅包含函数地址——主流实现中,vtable通常以type_info指针(用于dynamic_cast和typeid的RTTI信息)开头,随后才是虚函数指针列表。例如,一个简单类的vtable结构可能如下:
代码语言:txt
AI代码解释
Base vtable: [0] type_info* for Base // RTTI信息 [1] &Base::func1 // 虚函数指针 [2] &Base::func2 // 虚函数指针vtable的关键特性包括:
- 类级唯一性:每个类(含派生类)有且仅有一个vtable,所有对象共享该表;
- 编译期生成:编译器在编译阶段确定vtable大小及内容,运行时只读;
- 继承关联性:派生类vtable与基类vtable存在结构性关联,是实现多态的基础。
虚函数指针(vptr)
每个包含虚函数的对象,都会隐含一个虚函数指针(vptr),用于指向其所属类的vtable。vptr的初始化与维护由编译器自动完成:
- 初始化时机:对象构造过程中,在基类构造函数执行前(或执行中,依编译器实现),vptr被设置为指向当前类的vtable;
- 存储位置:通常位于对象内存布局的起始位置(如64位系统中,对象首8字节为vptr),但不同编译器可能有差异(如存在虚基类时位置可能调整);
- 隐藏性:vptr是编译器自动添加的隐藏成员,无法在用户代码中直接访问,但可通过调试工具或指针操作间接观察。
例如,一个包含vptr的对象内存布局(64位系统)通常为:
代码语言:txt
AI代码解释
+----------------+ // 起始地址 | vptr (8字节) | // 指向类的vtable +----------------+ | 成员变量1 | // 用户定义的成员 +----------------+ | 成员变量2 | +----------------+单继承中的虚函数机制
单继承是最常见的继承场景,其虚函数机制相对简单:派生类通过扩展基类的vtable,并替换重写的虚函数地址,实现多态。
基本结构与内存布局
以如下代码为例:
代码语言:cpp
AI代码解释
class Base { public: virtual void func1() {} // 虚函数1 virtual void func2() {} // 虚函数2 int base_data; // 数据成员 }; class Derived : public Base { public: void func1() override {} // 重写func1 virtual void func3() {} // 新增虚函数 int derived_data; // 派生类数据成员 };Base对象内存布局(64位系统,假设对齐为8字节):
- 首8字节:vptr(指向Base vtable);
- 接下来4字节:base_data(int类型);
- 填充4字节(满足8字节对齐);
- 总大小:16字节(8+4+4)。
Derived对象内存布局:
- 继承Base的所有成员(vptr、base_data、填充);
- 新增的derived_data(4字节);
- 填充4字节(对齐);
- 总大小:24字节(16+4+4)。
布局图示如下:
代码语言:txt
AI代码解释
Base对象: +----------------+ | vptr → Base vtable | // 8字节 +----------------+ | base_data | // 4字节 +----------------+ | (填充) | // 4字节(对齐到8字节) +----------------+ Derived对象: +----------------+ | vptr → Derived vtable | // 8字节(覆盖Base的vptr) +----------------+ | base_data | // 4字节(继承自Base) +----------------+ | (填充) | // 4字节(Base部分对齐) +----------------+ | derived_data | // 4字节(新增成员) +----------------+ | (填充) | // 4字节(整体对齐到8字节) +----------------+虚函数表的演变
vtable是单继承中多态实现的核心。派生类vtable并非独立创建,而是以基类vtable为基础扩展:
Base vtable结构
Base类的vtable包含RTTI信息和其声明的虚函数:
代码语言:txt
AI代码解释
Base vtable: [0] type_info* for Base // RTTI指针(用于typeid/ dynamic_cast) [1] &Base::func1 // 虚函数地址 [2] &Base::func2 // 虚函数地址Derived vtable结构
Derived类继承Base后,vtable发生如下变化:
- 重写的虚函数替换:Derived::func1覆盖Base::func1,占据原索引位置;
- 继承的虚函数保留:Base::func2未被重写,地址保持不变;
- 新增虚函数追加:Derived::func3添加到vtable末尾。
因此,Derived vtable结构为:
代码语言:txt
AI代码解释
Derived vtable: [0] type_info* for Derived // RTTI指针(更新为Derived类型) [1] &Derived::func1 // 重写:替换原Base::func1 [2] &Base::func2 // 继承:保持原地址 [3] &Derived::func3 // 新增:追加到末尾单继承虚函数调用流程
当通过基类指针调用虚函数时,多态通过vptr和vtable实现:
代码语言:cpp
AI代码解释
Base* ptr = new Derived(); // Base指针指向Derived对象 ptr->func1(); // 调用Derived::func1而非Base::func1底层流程解析:
- 获取vptr:从ptr指向的对象首地址读取vptr(即Derived vtable的地址);
- 索引vtable:根据func1在vtable中的固定索引(示例中为索引1),获取函数指针;
- 调用函数:通过函数指针执行Derived::func1。
这一过程的关键在于索引位置固定:派生类不会改变继承的虚函数在vtable中的索引,确保基类指针能正确定位到派生类重写的函数。
单继承虚函数机制的特点
- 单一vptr:整个继承链中仅需一个vptr,所有虚函数通过该指针访问;
- vtable扩展式增长:派生类vtable是基类vtable的“超集”,结构清晰;
- 高效调用:虚函数调用仅需一次vptr解引用+固定索引访问,性能接近直接函数调用;
- 内存开销可控:对象仅增加一个vptr(8字节),vtable为类级共享,不占用对象内存。
多继承中的虚函数机制
多继承(一个派生类继承多个基类)显著增加了虚函数机制的复杂性。当多个基类均包含虚函数时,派生类需同时维护多个vtable和vptr,且需解决this指针调整等问题。
基本结构与内存布局
以双重继承为例:
代码语言:cpp
AI代码解释
class Base1 { public: virtual void func1() {} // 虚函数 virtual void func2() {} // 虚函数 int base1_data; // 数据成员 }; class Base2 { public: virtual void func3() {} // 虚函数 virtual void func4() {} // 虚函数 int base2_data; // 数据成员 }; class Derived : public Base1, public Base2 { // 多继承Base1和Base2 public: void func1() override {} // 重写Base1::func1 void func4() override {} // 重写Base2::func4 virtual void func5() {} // 新增虚函数 int derived_data; // 派生类数据成员 };Derived对象内存布局(64位系统,对齐8字节):
- 首先包含完整的Base1子对象(vptr1 + base1_data + 填充);
- 随后包含完整的Base2子对象(vptr2 + base2_data + 填充);
- 最后是Derived自身的数据成员derived_data及填充。
布局图示如下:
代码语言:txt
AI代码解释
Derived对象: +-------------------+ // Base1子对象开始 | vptr1 → Derived vtable (Base1部分) | // 8字节(Base1的vptr) +-------------------+ | base1_data | // 4字节(Base1数据) +-------------------+ | (填充) | // 4字节(Base1对齐到8字节) +-------------------+ // Base1子对象结束(总16字节) | vptr2 → Derived vtable (Base2部分) | // 8字节(Base2的vptr) +-------------------+ | base2_data | // 4字节(Base2数据) +-------------------+ | (填充) | // 4字节(Base2对齐到8字节) +-------------------+ // Base2子对象结束(总16字节,累计32字节) | derived_data | // 4字节(Derived数据) +-------------------+ | (填充) | // 4字节(整体对齐到8字节) +-------------------+ // 总大小:40字节(32+4+4)关键差异:多继承下对象包含多个vptr(每个有虚函数的基类贡献一个),分别对应不同基类的vtable视图。
多继承中的多个vtable
Derived类需要为每个基类维护独立的vtable“视图”,以确保不同基类指针能正确访问虚函数。
Base1部分的vtable
Base1作为第一个基类,其vtable视图包含:
- 重写的Base1::func1;
- 继承的Base1::func2;
- 新增的Derived::func5(追加到末尾)。
结构如下:
代码语言:txt
AI代码解释
Derived vtable (Base1视图): [0] type_info* for Derived // RTTI指针 [1] &Derived::func1 // 重写Base1::func1 [2] &Base1::func2 // 继承Base1::func2 [3] &Derived::func5 // 新增Derived::func5Base2部分的vtable
Base2作为第二个基类,其vtable视图需解决两个问题:
- 重写的Base2::func4需关联到Derived实现;
- 确保通过Base2指针调用时,this指针正确指向Derived对象。
因此,Base2视图的vtable结构为:
代码语言:txt
AI代码解释
Derived vtable (Base2视图): [0] type_info* for Derived // RTTI指针(与Base1视图共享) [1] &Base2::func3 // 继承Base2::func3 [2] &thunk_Derived::func4 // 重写Base2::func4(通过thunk函数)Thunk函数:解决this指针调整问题
多继承中最复杂的问题是this指针偏移。当通过Base2指针访问Derived对象时,该指针实际指向Derived对象中Base2子对象的起始位置(示例中为偏移16字节处),而非整个对象的起始位置。若直接调用Derived::func4,this指针将指向Base2子对象,导致访问Derived成员时地址错误。
Thunk函数(跳板函数)通过调整this指针解决这一问题。它是编译器生成的小辅助函数,执行以下操作:
- 将Base2指针调整为完整的Derived指针(减去Base2子对象在Derived中的偏移量);
- 跳转到实际的Derived::func4执行。
www.dongchedi.com/article/7598265380330848830
www.dongchedi.com/article/7598273072999186968
www.dongchedi.com/article/7598274901841379865
www.dongchedi.com/article/7598276424889336345
www.dongchedi.com/article/7598275637006664254
www.dongchedi.com/article/7598279236490723865
www.dongchedi.com/article/7598274890558800409
www.dongchedi.com/article/7598282614747251224
www.dongchedi.com/article/7598276969335669310
www.dongchedi.com/article/7598278977895121470
www.dongchedi.com/article/7598282709198717464
www.dongchedi.com/article/7598281407030936089
www.dongchedi.com/article/7598277471599346201
www.dongchedi.com/article/7598283447504896536
www.dongchedi.com/article/7598287089956192793
www.dongchedi.com/article/7598287774483202584
www.dongchedi.com/article/7598288632398856728
www.dongchedi.com/article/7598287158570435097
www.dongchedi.com/article/7598292330680418878
www.dongchedi.com/article/7598288380383855129
www.dongchedi.com/article/7598290710689219134
www.dongchedi.com/article/7598288042881073689
www.dongchedi.com/article/7598291946742317593
www.dongchedi.com/article/7598293637713478206
www.dongchedi.com/article/7598290400063373848
www.dongchedi.com/article/7598292454667944472
www.dongchedi.com/article/7598291773202776638