虛函數表是 C++ 多態的核心實現機制。本文通過內存佈局圖解、代碼驗證、匯編分析,系統講解單繼承、多繼承、菱形虛繼承下的 vptr/vtable 組織、指針調整、強制轉換陷阱,直擊面試高頻考點。
類 A: [vptr_A] → vtable_A: [A::func, A::funcA] 類 B: [vptr_B] → vtable_B: [B::func(override), B::funcB] 類 C: [vptr_C] → vtable_C: [C::func(override), C::funcC]
vptr (通常在對象首地址)typedef void(*FUNC)();
A a; B b; C c;
// 遍歷 vtable: *(int*)*(int*)&obj 取第一個虛函數地址
((FUNC)(*(int*)(*(int*)&a)))(); // A::func
((FUNC)(*((int*)(*(int*)&a) + 1)))(); // A::funcA
class A { virtual void func(); virtual void funcA(); int a; };
class B { virtual void func(); virtual void funcB(); int b; };
class C : public A, public B {
virtual void func(); virtual void funcC(); int c;
};
C 對象: [0] vptr_A → vtable_C_part_A: [C::func, C::funcA] [4] int a [8] vptr_B → vtable_C_part_B: [C::func, C::funcB] [12] int b [16] int c
C::func 同時出現在兩個 vtable (thunk 調整 this)C c;
A* pa = &c; // pa = &c (偏移 0)
B* pb = &c; // pb = &c + 4 (偏移 sizeof(A) = 8? 實際 4 字節對齊)
C* pc = &c; // pc = &c
pa 只能調用 A 的虛函數 (受類型限制)pb 只能調用 B 的虛函數pc 可調用所有pa->func() 調用 C::func (vtable_C_part_A[0])A* pa = &c;
B* pb = &c;
// 錯誤示範:reinterpret_cast 不調整指釖
pa = reinterpret_cast(pb); // pa 現在指向 B 子對象!
pa->func(); // 調用 vtable_C_part_B[0] → C::func (偶然正確)
pa->funcA(); // 讀取 vtable_C_part_B[1] → C::funcB (災難!)
教訓:多繼承下絕不可 用 `reinterpret_cast`/`C-style cast` 在基類指針間轉換,必須用 `dynamic_cast` (RTTI) 或 `static_cast` (已知層次)。
class A { virtual void func(); int a; };
class B : virtual public A { virtual void funcB(); int b; };
class C : virtual public A { virtual void funcC(); int c; };
class D : public B, public C { virtual void func(); int d; };
D: [0] vptr_B → vtable_D_B [4] int b [8] vptr_C → vtable_D_C [12] int c [16] int d [20] vptr_A (共享) → vtable_D_A [24] int a
A 只有一份,放在最末尾| 問題 | 答案要點 |
|---|---|
| 虛函數表存放位置 | 只讀數據段 (.rodata),進程共享 |
| 對象大小增加多少 | 單繼承 +1 vptr (4/8字節),多繼承 +N vptr |
| 構造函數能否虛 | 不能,vptr 未初始化;析構函數必須虛 |
| 多繼承指針轉換開銷 | static_cast 編譯期調整指針值,零運行開銷 |
| 虛繼承解決什麼 | 菱形繼承數據冗余與二義性 |
| 純虛函數 vtable 槽位 | 存放 `__cxa_pure_virtual`,調用即終止 |
info vtbl obj 查看虛表obj, vfptrg++ -fdump-class-hierarchy / cl /d1 reportSingleClassLayout