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

VC++ 物件模型深度解析:類別佈局、成員存取、虛函數、繼承、例外處理實作機制

     編輯:VC 教學

前言

理解 C++ 編譯器如何將類別轉換為記憶體佈局、函數呼叫、繼承關係,是進階除錯、效能調優、跨語言互通的基石。本文基於 Microsoft VC++ 實作,系統解析:**C 相容結構、單一/多重/虛擬繼承佈局**、**成員變數/函數存取**、**虛函數 vtable/vptr**、**調整器**、**建構/解構/拷貝語意**、**陣列 new/delete**、**例外處理支援**,並對比其他編譯器差異。

1. C 結構體佈局 (基線)


struct A { char c; int i; };
// VC++ 佈局 (32位元, 預設 4 位元組對齊):
// offset 0: char c (1 byte)
// offset 1-3: padding (3 bytes)
// offset 4: int i (4 bytes)
// sizeof(A) = 8
  • 成員**宣告順序** = 記憶體順序。
  • **對齊規則**:各成員按其型別大小對齊 (char=1, short=2, int/pointer=4/8, double=8)。
  • 結構體大小 = 最大成員對齊需求的倍數 (尾部填充)。
  • 所有 C/C++ 編譯器對有效 C 結構體**保證相同佈局** (ABI 相容)。

2. 單一繼承佈局


class Base { int b; virtual void f(); };
class Derived : public Base { int d; virtual void f(); virtual void g(); };

// 記憶體佈局 (32位元):
// Derived 物件:
// +0: vptr → Derived vtable
// +4: Base::b
// +8: Derived::d
// sizeof(Derived) = 12
  • **基類子物件**位於衍生類物件**起始位址** (單一繼承無位移)。
  • vptr 指向**衍生類 vtable** (含覆寫版本)。
  • 基類 vptr 在建構期間暫指向基類 vtable,建構完成後更新為衍生類 vtable。

3. 多重繼承佈局


class Base1 { int b1; virtual void f1(); };
class Base2 { int b2; virtual void f2(); };
class Derived : public Base1, public Base2 { int d; virtual void f1(); };

// 佈局:
/// +0: vptr1 → Derived vtable (for Base1 subobject)
// +4: Base1::b1
// +8: vptr2 → Derived vtable (for Base2 subobject)  -- 第二個 vptr!
// +12: Base2::b2
// +16: Derived::d
// sizeof(Derived) = 20
  • 每個**直接基類**若含虛函數,**各自擁有 vptr**。
  • 衍生類物件包含**多個基類子物件**,各自有獨立位移。
  • Base2* 指向衍生類物件時,**指標需調整 +8** (此例)。

4. 虛擬繼承佈局


class Base { int b; virtual void f(); };
class Derived1 : virtual public Base { int d1; };
class Derived2 : virtual public Base { int d2; };
class MostDerived : public Derived1, public Derived2 { int md; };

// 佈局 (簡化):
// MostDerived:
// +0: vptr (Derived1 vtable)
// +4: Derived1::d1
// +8: vbptr (虛基類指標表指標) → vbtable
// +12: Derived2::vptr
// +16: Derived2::d2
// +20: MostDerived::md
// +24: Base::b (共享單一實例,位於最尾端)
// sizeof = 28
  • 虛基類 **Base 僅出現一次** (共享),位於最衍生類物件**末端**。
  • 每個虛繼承路徑引入 **vbptr** (指向 vbtable),vbtable 記錄虛基類位移。
  • 轉型 Base* 需透過 vbptr/vbtable **動態計算位移** (執行期開銷)。

5. 成員變數存取

  • 非靜態資料成員:編譯期計算 **offset**,存取為 *(this + offset)。單一繼承 offset 固定;虛繼承需透過 vbtable 查詢 (間接)。
  • 靜態資料成員:全域儲存,不屬於物件,直接用修飾名存取。

6. 成員函數存取

7. 調整器 — 多重/虛繼承指標轉換的隱形代價


class Base1 { virtual void f(); };
class Base2 { virtual void g(); };
class Derived : public Base1, public Base2 {};

Derived d;
Base2* p2 = &d; // 編譯器插入調整器:p2 = (Base2*)((char*)&d + 8)
p2->g();        // 正常呼叫

// 若透過函數指標/虛函數呼叫需調整 this:
// 編譯器生成 thunk: 
//   adjustor_thunk: add ecx, 8; jmp Derived::g
  • **調整器** 是微小程式碼片段,修正 this 指標位移後跳轉真實函數。
  • 產生於:**多重繼承** 基類指標轉換、**虛繼承** 基類指標轉換、**虛函數** 透過基類 vtable 呼叫衍生類覆寫版本時。
  • **開銷**:極小 (幾條指令),但破壞指標比較 (p1 == p2 可能為 false)。

8. 建構/解構/拷貝語意

  • 建構順序:虛基類 (深度優先、左到右) → 非虛基類 (宣告順序) → 成員 (宣告順序) → 本體。
  • 期間 **vptr 逐層更新**:基類建構時指向基類 vtable,衍生類建構時更新為衍生類 vtable。**建構期間呼叫虛函數 → 呼叫當前類別版本** (衍生類版本尚未生效)。
  • 解構順序:完全相反。解構期間 vptr 逐層還原,**解構期間呼叫虛函數 → 呼叫當前類別版本**。
  • 拷貝建構/賦值:預設逐位元複製 (shallow)。含指標/資源需自訂深拷貝 (Rule of Three/五)。

9. 陣列 new/delete


T* p = new T[10]; // 配置:大小 = 10*sizeof(T) + cookie (元素計數)
delete[] p;       // 讀取 cookie,逆序呼叫 10 次解構函數
  • VC++ 在陣列前置 **cookie (size_t)** 記錄元素數,delete[] 依此迴圈解構。
  • new T[0] 合法,回傳非空唯一指標。

10. 例外處理支援

  • VC++ 使用 **SEH (Structured Exception Handling)** 實現 C++ 例外。
  • try/catch 區塊對應 **__try/__except** 篩選器。
  • 拋出例外:_CxxThrowException 建立例外物件、呼叫 RaiseException (SEH 碼 0xE06D7363)。
  • 堆疊展開:運行時走訊框架資訊 (.pdata/.xdata),呼叫 **清理函數** (解構區域物件)、**catch 篩選器** 比對型別。
  • catch(...) 對應篩選器永真。
  • **開銷**:無例外路徑幾乎零成本 (零開銷原則);拋出路徑較重 (堆疊走訪、型別比對)。

編譯器差異提醒

  • **GCC/Clang (Itanium C++ ABI)**:vtable 佈局、虛繼承實作、調整器形式、例外處理 (DWARF/.eh_frame) 與 VC++ **完全不同**。
  • 跨 DLL 介面**嚴禁**傳遞 C++ 類別物件 (含虛函數/繼承),除非雙方**同編譯器版本、同 CRT、同編譯選項**。
  • 建議跨模組介面使用 **C 風格 API** (不透明指標 + 純虛介面類別僅定義虛函數、無資料成員)。

總結

VC++ 物件模型核心:**單一繼承零位移、多重繼承多 vptr、虛繼承共享基類+vbptr**、**虛函數 vtable 動態綁定**、**調整器修正 this**、**建構/解構期 vptr 逐層更新/還原**、**陣列 cookie**、**SEH 例外**。深入理解佈局與呼叫機制,能精準預測記憶體佔用、避免指標比較陷阱、正確設計跨模組介面、優化關鍵路徑 (如熱點虛函數去虛擬化)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
函數類型呼叫機制this 傳遞
非虛擬非靜態直接呼叫 (靜態綁定)隱含第一參數
虛擬函數this->vptr[index]() (動態綁定)隱含第一參數
靜態成員函數直接呼叫 (無 this)無