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

C++ 類物件記憶體模型深度剖析:佈局、繼承與虛函數表

     編輯:C++ 入門

核心原則

C++ 標準僅規定語義行為,**不強制記憶體佈局**。但主流編譯器 (MSVC, GCC, Clang, ICC) 遵循 Itanium C++ ABI (非 Windows) 或 MSVC ABI (Windows),形成事實標準。

基礎佈局規則

  1. 非靜態數據成員:按聲明順序依次排列,受 alignas/#pragma pack 影響對齊。
  2. 靜態數據成員:不佔用物件大小,存儲於全域/靜態存儲區(單一定義規則 ODR)。
  3. 非虛函數:不佔用物件大小,屬於類別,編譯期靜態綁定。
  4. 虛函數:引入 vptr (虛表指標),通常放在物件**最前端**(MSVC/Itanium 均如此),指向類別級的 vtable (虛函數表)。

單繼承佈局

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

// 記憶體佈局 (MSVC x64)
Derived:
+0  vptr ──────► Derived::vtable [ &Derived::f, ... ]
+8  Base::a
+12 Derived::b
// 大小: 16 (含對齊)
  • 基類子物件併入派生類物件,**單一 vptr**。
  • 派生類覆寫虛函數 → vtable 對應槽位替換為派生類函數位址。
  • 新增虛函數 → vtable 尾部擴展。

多重繼承佈局

class Base1 { virtual void f1(); int a; };
class Base2 { virtual void f2(); int b; };
class Derived : public Base1, public Base2 { virtual void f1(); int c; };

// 佈局
Derived:
+0  vptr1 ──────► Derived::vtable_for_Base1 [ &Derived::f1, ... ]
+8  Base1::a
+16 vptr2 ──────► Derived::vtable_for_Base2 [ &Derived::f2, ... ]  // 注意:此 vtable 可能共用或獨立
+24 Base2::b
+32 Derived::c
  • 每個直接基類帶虛函數 → 派生類物件包含**多個 vptr**,分別指向對應基類的 vtable 視圖。
  • 轉型調整:Base2* p2 = static_cast(pDerived) → 指標偏移 +16 (this pointer adjustment)。
  • 第一個基類 (Base1) 通常共用派生類主 vptr (Primary Base Optimization)。

虛繼承佈局

class Base { virtual void f(); int a; };
class Derived1 : virtual public Base { int b; };
class Derived2 : virtual public Base { int c; };
class Final : public Derived1, public Derived2 { int d; };

// 佈局 (簡化)
Final:
+0  vptr1 ──────► vtable for Derived1
+8  Derived1::b
+16 vptr2 ──────► vtable for Derived2
+24 Derived2::c
+32 vbptr ──────► Virtual Base Table (vbtable) → 指向共享 Base 子物件偏移
+40 Final::d
+48 Base::a (共享單一實例)
+56 Base::vptr (或由 Final 的 vtable 統一管理)
  • 引入 vbptr (虛基類表指標),指向 vbtable,記錄虛基類子物件相對偏移。
  • 虛基類**僅存在一份**,位於最派生類物件末尾(或按 ABI 安排)。
  • 構造順序:虛基類 → 非虛基類 → 派生類。虛基類由**最派生類構造函數**直接初始化。

空類別與尾部填充

  • sizeof(Empty) == 1:確保不同物件擁有不同位址。
  • 空基類優化 (EBO):class Derived : Empty { int x; }; → sizeof(Derived) == sizeof(int)。空基類不佔用空間(前提:無虛函數、非虛繼承)。

實用調試技巧

MSVC: /d1reportSingleClassLayout<ClassName>

cl /d1reportSingleClassLayoutDerived test.cpp
// 輸出詳細佈局:偏移、大小、vptr/vbptr 位置、基類子物件結構

GCC/Clang: -fdump-class-hierarchy / -fdump-lang-class

g++ -fdump-class-hierarchy test.cpp
# 生成 test.cpp.001t.class 文件,含完整類別層次與佈局

運行期驗證

Derived d;
std::cout << "Size: " << sizeof(d) << std::endl;
std::cout << "vptr offset: " << (char*)&d - (char*)*(void**)&d << std::endl; // 簡易驗證

對開發者的影響

場景影響對策
跨 DLL 傳遞 C++ 物件ABI 不兼容 (vtable 位址、佈局差異)純 C 介面 / COM / 統一編譯器版本與標準庫
序列化/網路傳輸指標、vptr、填充位元不可直接複製POD 結構體 / 显式序列化 (Protobuf, FlatBuffers, cereal)
效能優化繼承深度、虛函數調用間接開銷、緩存行未命中組合優於繼承、熱點路徑 final/CRTP 消除虛調用、數據導向設計 (DOD)
逆向工程/安全vtable 位址洩漏類別資訊、RTTI 洩漏名稱編譯器選項隱藏符號、控制流完整性 (CFI)、去除 RTTI (-fno-rtti)

總結

C++ 物件模型核心:**非靜態成員按序排列 + 虛函數引入 vptr/vtable + 繼承引入子物件組合與指標調整 + 虛繼承引入 vbptr/vbtable 共享單一基類**。理解佈局,才能寫出高效、可移植、安全的系統級代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved