C++笔记 详解虚基表跟虚函数表
我来详细解析虚函数表(vtable)和虚基类表(vbtable)的底层实现。
1. 虚函数表 (vtable) 详解
基本结构和原理
cpp
#include <iostream>
using namespace std;class Base {
public:int base_data;Base() : base_data(100) {}virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }virtual ~Base() { cout << "Base::~Base" << endl; }
};class Derived : public Base {
public:int derived_data;Derived() : derived_data(200) {}void func1() override { cout << "Derived::func1" << endl; }void func2() override { cout << "Derived::func2" << endl; }virtual void func3() { cout << "Derived::func3" << endl; }
};// 手动模拟vtable的结构
struct VTable {void (*func1)(void*);void (*func2)(void*); void (*destructor)(void*);void (*func3)(void*); // 派生类新增的虚函数
};内存布局分析
cpp
void analyzeVTable() {Base base;Derived derived;cout << "=== 对象大小分析 ===" << endl;cout << "Base大小: " << sizeof(base) << " (数据:" << sizeof(int) << " + vptr:" << sizeof(void*) << " + 对齐)" << endl;cout << "Derived大小: " << sizeof(derived) << endl;cout << "\n=== 内存地址分析 ===" << endl;cout << "Base对象地址: " << &base << endl;cout << "Base数据地址: " << &base.base_data << endl;cout << "Derived对象地址: " << &derived << endl; cout << "Base部分数据: " << &derived.base_data << endl;cout << "Derived数据: " << &derived.derived_data << endl;// 获取vptr(平台相关代码)void** base_vptr = *(void***)&base;void** derived_vptr = *(void***)&derived;cout << "\n=== vtable地址 ===" << endl;cout << "Base vtable: " << base_vptr << endl;cout << "Derived vtable: " << derived_vptr << endl;
}vtable的创建过程
cpp
/* Base的vtable: [0] → Base::func1()地址 [1] → Base::func2()地址 [2] → Base::~Base()地址Derived的vtable: [0] → Derived::func1()地址 // 重写 [1] → Derived::func2()地址 // 重写 [2] → Derived::~Derived()地址 // 重写 [3] → Derived::func3()地址 // 新增 */
2. 虚基类表 (vbtable) 详解
基本结构和原理
cpp
#include <iostream>
using namespace std;class VirtualBase {
public:int vb_data;VirtualBase() : vb_data(999) {}
};class A : virtual public VirtualBase {
public:int a_data;A() : a_data(100) {}
};class B : virtual public VirtualBase {
public:int b_data; B() : b_data(200) {}
};class C : public A, public B {
public:int c_data;C() : c_data(300) {}
};// 手动模拟虚基类表
struct VBTable {int offset_to_top; // 到对象顶部的偏移量int offset_to_vbase; // 到虚基类的偏移量
};内存布局分析
cpp
void analyzeVBTable() {C c_obj;cout << "=== 对象大小和地址 ===" << endl;cout << "C对象大小: " << sizeof(c_obj) << endl;cout << "C对象地址: " << &c_obj << endl;A* a_ptr = &c_obj;B* b_ptr = &c_obj;VirtualBase* vb_ptr = &c_obj;cout << "A* 视角: " << a_ptr << endl;cout << "B* 视角: " << b_ptr << endl; cout << "VirtualBase* 视角: " << vb_ptr << endl;cout << "\n=== 偏移量计算 ===" << endl;cout << "A到C的偏移: " << (char*)&c_obj - (char*)a_ptr << endl;cout << "B到C的偏移: " << (char*)&c_obj - (char*)b_ptr << endl;cout << "VirtualBase到C的偏移: " << (char*)&c_obj - (char*)vb_ptr << endl;// 模拟编译器如何通过vbtable找到虚基类cout << "\n=== 虚基类查找模拟 ===" << endl;cout << "从A找到VirtualBase: " << (char*)vb_ptr - (char*)a_ptr << endl;cout << "从B找到VirtualBase: " << (char*)vb_ptr - (char*)b_ptr << endl;
}3. 底层实现对比
编译器生成的代码模拟
cpp
// 模拟编译器如何实现虚函数调用
void simulatedVirtualCall(Base* obj) {// 实际编译器生成的代码类似:void** vptr = *(void***)obj; // 获取vptrvoid (*func)(void*) = (void(*)(void*))vptr[0]; // 获取第一个虚函数func(obj); // 调用函数
}// 模拟编译器如何访问虚基类成员
void simulatedVBaseAccess(A* a_ptr) {// 实际编译器生成的代码类似:void** vbptr = *(void***)a_ptr; // 获取vbptrint offset_to_vbase = *(int*)((char*)vbptr + sizeof(int)); // 从vbtable取偏移量VirtualBase* vb_ptr = (VirtualBase*)((char*)a_ptr + offset_to_vbase);int data = vb_ptr->vb_data; // 访问虚基类成员
}4. 完整的内存布局示例
cpp
class Base {
public:int base_data;virtual void vfunc1() {}virtual void vfunc2() {}
};class VirtualBase {
public:int vb_data;
};class Derived : public Base, virtual public VirtualBase {
public:int derived_data;virtual void vfunc1() override {}virtual void vfunc3() {}
};/*
Derived对象的完整内存布局(典型实现):[0] vptr_Base → Base的vtable
[8] base_data
[12] derived_data
[16] vptr_Derived → Derived的vbtable
[24] vb_dataBase的vtable:
[0] Derived::vfunc1()地址
[1] Base::vfunc2()地址
[2] 析构函数地址Derived的vbtable:
[0] 到对象顶部的偏移量
[1] 到VirtualBase的偏移量
*/5. 实际编译器输出分析
cpp
#include <iostream>
using namespace std;// 简单的测试类
class TestBase {
public:virtual void func() {}
};class TestVBase {
public:int data;
};class TestDerived : public TestBase, virtual public TestVBase {
public:virtual void func() override {}
};void testMemoryLayout() {TestDerived obj;cout << "=== 实际内存分析 ===" << endl;cout << "TestBase大小: " << sizeof(TestBase) << endl;cout << "TestVBase大小: " << sizeof(TestVBase) << endl; cout << "TestDerived大小: " << sizeof(TestDerived) << endl;cout << "对象地址: " << &obj << endl;cout << "TestBase部分: " << static_cast<TestBase*>(&obj) << endl;cout << "TestVBase部分: " << static_cast<TestVBase*>(&obj) << endl;// 使用gcc的扩展来直接查看vtable(如果支持)// void** vptr = (void**)*(void**)&obj;// cout << "vtable[0]: " << vptr[0] << endl;
}int main() {analyzeVTable();cout << "\n" << string(50, '=') << "\n" << endl;analyzeVBTable();cout << "\n" << string(50, '=') << "\n" << endl;testMemoryLayout();return 0;
}6. 关键区别总结
| 特性 | 虚函数表 (vtable) | 虚基类表 (vbtable) |
|---|---|---|
| 目的 | 实现运行时多态 | 解决菱形继承问题 |
| 存储内容 | 函数指针数组 | 偏移量数组 |
| 指针位置 | 对象开头(vptr) | 虚继承类的子对象内(vbptr) |
| 访问方式 | 间接函数调用 | 地址计算+直接访问 |
| 性能开销 | 一次间接调用 | 地址计算+内存访问 |
7. 实际应用建议
cpp
// 好的设计:合理使用虚继承
class Interface {
public:virtual void operation() = 0;virtual ~Interface() = default;
};class BaseImpl : virtual public Interface {// 共享实现
};class ExtendedImpl : public BaseImpl {// 扩展功能
};// 避免过度使用虚继承,因为:
// 1. 增加内存开销(vbptr + vbtable)
// 2. 降低访问速度(需要偏移量计算)
// 3. 构造函数调用复杂化