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

C++ 類大小完全指南:空類、虛函數、單/多繼承、虛繼承的記憶體佈局實測

     編輯:關於C語言

前言:sizeof 不會撒謊

C++ 類的大小由**非靜態數據成員**、**虛指針 (vptr)**、**虛基類指針 (vbptr)**、**對齊填充**共同決定。靜態成員、非虛函數**不佔用對象大小**。本文通過系統性測試用例,揭示各種繼承場景下的記憶體佈局規律(基於 32 位 MSVC/GCC,64 位指針為 8 字節,結構同理)。

基礎測試矩陣

類別定義sizeof解析
空類class Empty {}1C++ 標準要求完整對象擁有非零大小,確保不同對象地址唯一
帶虛函數class A { virtual void f(); }4 (32位) / 8 (64位)含一個 vptr 指向虛表
靜態成員class S { static int x; }1靜態成員屬於類,不屬於對象
虛函數+靜態class VS { virtual void f(); static int x; }4/8僅 vptr 佔用空間
普通數據成員class D { bool b; int i; static int s; }8bool(1) + padding(3) + int(4),對齊到 4

單繼承:非虛 vs 虛

class Base { virtual void f(); }; // sizeof=4/8 (vptr)
class Derived : public Base {};   // sizeof=4/8 —— 單繼承無新增 vptr,共用基類 vptr
class VDerived : virtual public Base {}; // sizeof=8/16 —— **新增 vbptr** 指向虛基類偏移表
  • 非虛單繼承:子類對象佈局 = 基類子對象 + 派生成員。**vptr 共用**,大小不變(無新成員時)。
  • 虛單繼承:引入 vbptr (虛基類指針),用於運行時查找虛基類子對象偏移。大小 = 基類大小 + 指針大小。

多重繼承:非虛

class A { virtual void f(); }; // 4/8
class B { virtual void g(); }; // 4/8
class C : public A, public B {}; // sizeof=8/16 —— **兩個 vptr**!
  • 每個帶虛函數的直接基類貢獻一個 **vptr**。
  • 子類對象包含多個基類子對象,各有各的 vptr。
  • 一個類**只有一張虛表**,但**每個對象可能有多個 vptr**(指向同一虛表的不同部分,或不同虛表)。

多重繼承混合虛繼承

class C1 : public A, virtual public B {}; // sizeof=12/24 (A:4/8 + B:4/8 + vbptr:4/8)
class C2 : virtual public A, virtual public B {}; // sizeof=12/24 (同樣:兩基類 + 一個 vbptr)
  • **只要有虛繼承,就引入一個 vbptr**(指向虛基類偏移表),無論虛基類數量。
  • 非虛基類正常佈局(含 vptr),虛基類通過 vbptr 間接訪問。

虛繼承的共享機制鑑證

class Base { virtual void f(); };
class B1 : virtual public Base {}; // sizeof=8/16 (Base vptr + vbptr)
class B2 : virtual public Base {}; // sizeof=8/16

class D : public B1, public B2 {}; // sizeof=16/32
// D 中只有一份 Base 子對象!
// B1::vbptr 和 B2::vbptr 指向同一虛基類偏移表條目
// D 的佈局:B1(vptr+vbptr) + B2(vptr+vbptr) + Base(vptr)
  • 菱形繼承:虛繼承確保共同虛基類**只有一份實體**。
  • 後續繼承若再次 virtual public Base,**不再增加 vbptr**,複用現有虛基類偏移表。

嵌套虛繼承驗證

class C3 : public B1 {};           // sizeof=8/16 (繼承 B1 的 vbptr,共享 Base)
class C4 : virtual public B1 {};   // sizeof=12/24 (新增 vbptr!指向 B1 的虛基類偏移表)
class C5 : virtual public B1, virtual public Base {}; // sizeof=12/24 (等價於 C4,Base 已被 B1 虛繼承)
  • virtual public B1 視 B1 為「虛基類」,需新增 vbptr 定位 B1 子對象(B1 內含 Base)。
  • virtual public Base 在 virtual public B1 後**不再增加開銷**,編譯器識別 Base 已經是間接虛基類。

虛表指針一致性驗證

A a1, a2;
if (&a1 == &a2) // 錯誤比較!這比較對象地址
// 正確:比較 vptr 內容
// 同類不同對象:vptr 指向**同一張虛表**(只讀數據段),故 vptr 值相同
// 不同類(即使虛函數簽名相同):虛表不同,vptr 不同

總結:類大小計算口訣

  1. 從 1 開始(空類)。
  2. 加上**非靜態數據成員**大小(含對齊填充)。
  3. 每個**直接基類**若有虛函數 → **+1 vptr**。
  4. 若有**虛繼承**(直接或間接)→ **+1 vbptr**(整個類層次只加一次)。
  5. 最終按**最大對齊數**對齊(通常為指針大小)。

實戰建議

  • 避免不必要的虛繼承(增加 vbptr、間接訪問開銷、佈局複雜)。
  • 多重繼承優先考慮**組合**或**介面類**(純虛、無數據、單繼承)。
  • 關注對象大小對緩存行(64B)的影響:高頻小對象盡量精簡。
  • 調試時用 sizeof、offsetof、__vptr 觀察佈局,而非憑直覺。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved