C++ 類的大小由**非靜態數據成員**、**虛指針 (vptr)**、**虛基類指針 (vbptr)**、**對齊填充**共同決定。靜態成員、非虛函數**不佔用對象大小**。本文通過系統性測試用例,揭示各種繼承場景下的記憶體佈局規律(基於 32 位 MSVC/GCC,64 位指針為 8 字節,結構同理)。
| 類別 | 定義 | sizeof | 解析 |
|---|---|---|---|
| 空類 | class Empty {} | 1 | C++ 標準要求完整對象擁有非零大小,確保不同對象地址唯一 |
| 帶虛函數 | 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; } | 8 | bool(1) + padding(3) + int(4),對齊到 4 |
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** 指向虛基類偏移表
class A { virtual void f(); }; // 4/8
class B { virtual void g(); }; // 4/8
class C : public A, public B {}; // sizeof=8/16 —— **兩個 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)
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 不同
sizeof、offsetof、__vptr 觀察佈局,而非憑直覺。