C++ 將數據與函數封裝在類中,編譯器背後如何佈局內存?普通函數、成員函數、虛函數調用機制有何不同?static 成員又如何存放?搞清這些底層細節,能寫出更高效、更安全的代碼,也是面試必考硬核知識。
class Point3d {
float x, y, z; // 直接內含於對象
public:
void print() { /* 不佔用對象空間 */ }
};
// sizeof(Point3d) == 12 (3*float)
class A { static int x; }; // sizeof(A) == 1 (空類佔1字節)
class C { virtual void foo(); }; // sizeof(C) == 8 (int + vptr)
確保不同對象擁有唯一地址:&a != &b。編譯器隱式插入 1 byte char。
class A { char x; int y; char z; }; // sizeof=12 (4字節對齊)
class B { char x; char y; int z; }; // sizeof=8 (優化後)
class D { long long x; char y; char z; }; // sizeof=16 (8字節對齊)
編譯器自動重排以減少填充,但 C++ 標準不保證成員聲明順序即內存順序(僅保證同訪問級別下順序一致)。
class X {
int a, b;
virtual void foo1() { cout << "X::foo1"; }
};
// 對象佈局:[vptr][a][b]
// vptr → 虛表:[&X::foo1]
驗證代碼:強制轉型 int** tmp = (int**)&obj,tmp[0][0] 即虛表地址,tmp[0][0][0] 為第一個虛函數地址。
class A { int x; };
class B : public A { int y; }; // sizeof(B)=8, 佈局:[A::x][B::y]
class C { virtual void fooC(); };
class D : public C { virtual void fooD(); };
// D 佈局:[vptr][C::fooC][D::fooD] → 虛表合併,覆蓋同名槽位
class A { virtual void fooA(); };
class B { virtual void fooB(); };
class C : public A, public B { virtual void fooC(); };
// C 佈局:[A::vptr][A::a][B::vptr][B::b][C::c]
// 兩個虛表指針,分別指向 A、B 子物件虛表
class A { int a1, a2; };
class B : virtual public A { int b; };
class C : virtual public A { int c; };
class D : public B, public C { int d; };
// D 佈局:[B::vptr][B::b][C::vptr][C::c][D::d][A::a1][A::a2]
// 虛基類 A 共享單份實例,放在最末尾,vptr 指向虛基類偏移表
通過 int** tmp = (int**)&obj 暴力解析對象內存:
tmp[0] → 第一個虛表指針tmp[0][i] → 第 i 個虛函數地址tmp[1]、tmp[2] 對應各基類子物件 vptr下一篇將深入「成員篇」:this 指針調整、靜態/非靜態成員訪問、虛函數調用開銷分析。