虚函数表(vtable)在C++中的生成时机如下:
1. 编译阶段生成
虚函数表是由编译器在编译阶段为每个包含虚函数的类生成的静态数据结构。具体来说:
- 类定义处理:当编译器解析到类的定义中包含虚函数(包括
virtual
成员函数或继承自基类的虚函数)时,会为该类创建一个虚函数表。 - 表内容确定:虚函数表中存储的是指向该类所有虚函数的指针,顺序与虚函数在类中的声明顺序一致。基类的虚函数在前,派生类新增的虚函数在后。
- 虚函数表存储位置:虚函数表通常被放置在程序的只读数据段(如
.rodata
或.data
段),在程序加载到内存时已存在。
2. 对象构造时关联
- 虚指针(vptr)的初始化:
每个包含虚函数的类的对象内部会隐式包含一个指向其虚函数表的指针(称为vptr
)。这个vptr
在对象构造时被初始化:- 构造函数中插入代码:编译器会在类的构造函数中插入代码,将
vptr
指向当前类对应的虚函数表。 - 继承链的处理:在派生类的构造函数中,
vptr
会依次指向基类和派生类的虚函数表(按构造顺序调整)。
- 构造函数中插入代码:编译器会在类的构造函数中插入代码,将
3. 运行时动态绑定
- 多态调用机制:
通过基类指针或引用调用虚函数时,程序会根据对象的vptr
找到对应的虚函数表,再从表中查找目标函数的地址,实现运行时多态。 - 虚函数表不变:虚函数表本身在运行时是只读的,其内容在编译时已完全确定,不会在运行时修改。
示例代码分析
class Base {
public:virtual void func1() {}virtual void func2() {}
};class Derived : public Base {
public:void func1() override {} // 覆盖基类func1virtual void func3() {} // 新增虚函数
};int main() {Base* obj = new Derived();obj->func1(); // 运行时通过vptr查找Derived的虚函数表,调用Derived::func1delete obj;return 0;
}
- 编译阶段:
编译器会为Base
和Derived
分别生成虚函数表:Base
的虚函数表:[Base::func1, Base::func2]
Derived
的虚函数表:[Derived::func1, Base::func2, Derived::func3]
- 对象构造时:
创建Derived
对象时,其vptr
会被初始化为指向Derived
的虚函数表。
4. 关键总结
阶段 | 行为 |
---|---|
编译阶段 | 为每个含虚函数的类生成虚函数表,确定表中函数指针的顺序和内容。 |
构造对象时 | 初始化对象的vptr ,使其指向所属类的虚函数表。 |
运行时 | 通过vptr 查表实现多态调用,虚函数表内容不可变。 |
常见问题
Q:虚函数表会被继承吗?
- 是的。派生类会继承基类的虚函数表,并在此基础上扩展(覆盖基类虚函数或新增虚函数)。
Q:虚函数表占用内存吗?
- 虚函数表本身是静态数据,每个类只有一份,占用固定内存。对象的
vptr
占用一个指针大小的内存(通常4或8字节)。
Q:能否手动修改虚函数表?
- 理论上可以通过指针操作修改,但属于未定义行为(UB),会导致程序崩溃或不可预测的结果。