首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 繼承與虛函數表深度解析:單繼承、多繼承、菱形繼承面試全景

     編輯:關於C++

前言

虛函數表是 C++ 多態的核心實現機制。本文通過內存佈局圖解、代碼驗證、匯編分析,系統講解單繼承、多繼承、菱形虛繼承下的 vptr/vtable 組織、指針調整、強制轉換陷阱,直擊面試高頻考點。

一、單繼承: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]
  • 每個類獨立 vtable,存放在只讀數據段
  • 對象僅含 vptr (通常在對象首地址)
  • 派生類覆寫虛函數會替換 vtable 對應槽位

代碼驗證 vtable 內容

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

二、多繼承:多 vptr、指針偏移

類定義

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 內存佈局

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
  • 兩個 vptr:分別指向 A 部分和 B 部分的 vtable
  • vtable 分為兩段:前段對應 A,後段對應 B
  • 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 對象佈局

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 只有一份,放在最末尾
  • 派生類 vtable 包含 vbase offset 表,指示虛基類相對位置
  • 構造順序:虛基類 → 直接基類 → 派生類

四、面試高頻題匯總

問題答案要點
虛函數表存放位置只讀數據段 (.rodata),進程共享
對象大小增加多少單繼承 +1 vptr (4/8字節),多繼承 +N vptr
構造函數能否虛不能,vptr 未初始化;析構函數必須虛
多繼承指針轉換開銷static_cast 編譯期調整指針值,零運行開銷
虛繼承解決什麼菱形繼承數據冗余與二義性
純虛函數 vtable 槽位存放 `__cxa_pure_virtual`,調用即終止

五、調試技巧

  • GDB: info vtbl obj 查看虛表
  • VS: 監視視窗輸入 obj, vfptr
  • 編譯器輸出佈局: g++ -fdump-class-hierarchy / cl /d1 reportSingleClassLayout

總結

  1. 單繼承:vptr 鏈線性,覆寫替換槽位
  2. 多繼承:多 vptr,基類指針需偏移,禁用 reinterpret_cast
  3. 虛繼承:共享基類,vbase offset 表間接訪問
  4. 面試核心:畫出內存圖、說明指針值變化、識別 UB 場景
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved