C++ 對象模型中,數據成員的內存佈局直接影響對象大小、繼承轉換、訪問性能。本文從空類、非靜態/靜態/常量成員、單繼承/多繼承/虛繼承、對齊填充、成員訪問機制六個維度深度剖析。
class Empty {};
// sizeof(Empty) == 1
// 原因:C++ 標準要求每個對象擁有唯一地址,編譯器注入 1 字節佔位符
Empty a, b;
assert(&a != &b); // 地址必然不同
編譯器按需生成特殊成員函數(默認構造/析構/拷貝/移動/賦值),僅在被使用時才生成代碼(ODR-used)。
| 成員類型 | 存儲位置 | 對象大小影響 | 備註 |
|---|---|---|---|
| 非靜態數據成員 | 對象內存內 | 是 | 按聲明順序佈局,受對齊影響 |
| 靜態數據成員 | 全局數據段 (.data/.bss) | 否 | 類共享單份實例,編譯期分配地址 |
| 常量數據成員 | 對象內存內 (非 static const 整型) | 是 | 每個對象可有不同值,構造函數初始化列表賦值 |
| 枚舉常量 | 編譯期常量,不佔對象內存 | 否 | 本質是編譯期整數常量 |
class Point {
public:
Point() : maxCount(10) {}
private:
int x; // 偏移 0
static int count; // 不在對象中
int y; // 偏移 4
const int maxCount; // 偏移 8 (每個對象可異)
enum { minCount = 2 }; // 不在對象中
};
// sizeof(Point) == 12 (x:4, y:4, maxCount:4)
// count 在 .bss 段,minCount 在 .rdata 段
class AA { protected: int x; char a; }; // sizeof=8 (x:4, a:1, padding:3)
class BB : public AA { protected: char b; }; // sizeof=12 (繼承8 + b:1 + padding:3)
class CC : public BB { protected: char c; }; // sizeof=16 (繼承12 + c:1 + padding:3)
關鍵現象:每層繼承獨立對齊,導致「階梯式填充」。
CC 內存佈局 (16 字節): [AA::x:4][AA::a:1][pad:3][BB::b:1][pad:3][CC::c:1][pad:3] 0-3 4 5-7 8 9-11 12 13-15
原因:若 CC 壓縮為 8 字節,基類子對象地址連續性被破壞,AA* p = new CC; p->x = 1; 寫入時會覆蓋 BB/CC 成員,導致未定義行為。編譯器犧牲空間換取基類子對象地址不變性,保證多態指針轉換安全。
class Base { int b; };
class Derived1 : virtual public Base { int d1; };
class Derived2 : virtual public Base { int d2; };
class Final : public Derived1, public Derived2 { int f; };
佈局特點:
Final 僅包含一份 Base::bDerived1/2 包含 虛基類指針 (vbptr) 指向共享的 Base 子對象Final 包含 vbptr 或 虛基類表指針 (vbtable pointer)
CC obj;
obj.x = 1; // 編譯為:*(int*)(&obj + offsetof(CC, x)) = 1;
// offsetof 在編譯期已知,零開銷
效率等同於 struct、C 風格結構體、單繼承鏈。
Final obj;
obj.b = 1; // 編譯為:
// 1. 獲取 vbptr
// 2. 查 vbtable 找到 Base 偏移
// 3. 計算 Base 地址
// 4. 寫入 b
// 多一次間接寻址,但僅虛基類成員受影響
關鍵區別:
vptr (通常 8 字節)vptr,指向衍生類 vtablevptrvptr 可能與 vbptr 合併或分離理解佈局機制,是掌握 sizeof、對象切片、多態指針轉換、序列化佈局兼容、高性能內存設計的基石。