C++ 中类的内存布局
在許多筆試面試中都會涉及到sizeof 運算符的求值問題。
這類問題主要分四類:
下文主要講述class 的內存布局,稍帶介紹一下struct 的size。
?
struct 的內存布局:
struct 的內存對齊和填充概念學過C 的都應該知道一點。其實只要記住一個概念和三個原則就可以了:
一個概念:
自然對齊:如果一個變量的內存地址正好位于它長度的整數倍,就被稱做自然對齊。
如果不自然對齊,會帶來CPU存取數據時的性能損失。(PS:具體應該與CPU通過總線讀寫內存數據的細節相關,具體沒有細究)
三個原則:
?
Class 的內存布局:
在學習C++ 的class 的內存布局前,先介紹下文會被用到的Visual studio 中的編譯選項"/d1reportAllClassLayout" 和 "/d1reportSingleClassLayout[ClassName]"。
這兩個編譯選項分別會輸出當前編譯單元中所以class 的內存布局和指定class 的內存布局。對于學習class 的內存布局很方便。
?
關于一個class 的定義,在定義過程中涉及到的有:
成員數據(靜態,非靜態)和成員函數(靜態,非靜態,virtual)。
所有的成員函數都不會占用對象的存儲空間,無論是靜態,非靜態還是虛函數。
而對于成員數據來說,只有非靜態的數據才會占用對象的存儲空間。
這個很好理解,靜態成員數據和成員函數是屬于class 的,而非屬于具體的對象,所以只要維護一份內存就可以了,無需每個對象都拷貝一份。
?
但是影響對象的大小的因素并不僅僅與看到的成員變量有關:
非靜態成員變量,虛函數表指針(_vftprt),虛基類表指針(_vbtptr),上文的內存對齊
?
-
空類
對于空類,許多人想當然的認為大小應該是0。這是錯誤的,如果是正確的話,這個類可以被實例化成一個對象,且這個對象不占任何存儲空間,且可以有很多不占任何空間的對象,而且這個不占空間的對象還可以有指針,這樣就很奇怪了。
所以正常編譯器會給空類分配1個byte 的空間用于標示。
sizeof(CEmpty) = 1
-
普通類
其類的布局如下:
class CBase size(4):+---0 | m_ia+---只有m_ia 成員,size 為4個byte。因為靜態數據成員和成員函數不占有對象空間。
-
有虛函數的類
其類的布局如下:
class CBase size(8):+---0 | {vfptr}4 | m_ia+---CBase::$vftable@:| &CBase_meta| 00 | &CBase::h可以看到該類的起始地址是放了一個"vfptr",這個指針用來指向該類的虛函數表。
-
單一繼承的類(無虛函數)
類的布局如下:
class CChild size(8):+---| +--- (base class CBase)0 | | m_ia| +---4 | m_iChild+---即派生類中拷貝了一份基類中的成員數據,所以size 為8個byte。
-
單一繼承的類(含有虛函數)
其類的布局如下:
class CChild size(12):+---| +--- (base class CBase)0 | | {vfptr}4 | | m_ia| +---8 | m_iChild+---CChild::$vftable@:| &CChild_meta| 00 | &CChild::{dtor} 1 | &CBase::f 2 | &CChild::g可以看到派生類中只有一個"vfptr",但是虛函數表中的函數卻不同于基類中的函數,沒有重寫的虛函數沿用基類中的虛函數,而被重寫的虛函數則更新為派生類中的虛函數。
-
多重繼承的類(基類都含有虛函數)
class CBase1 { public:int m_i1; public:virtual ~CBase1();virtual void f1();virtual void g1(); };class CBase2 { public:int m_i2; public:virtual ~CBase2();virtual void f2();virtual void g2(); };class CChild :public CBase1, public CBase2 { public:int m_iChild; public:virtual ~CChild();virtual void f1();virtual void g2(); };
其類的布局如下:
class CChild size(20):+---| +--- (base class CBase1)0 | | {vfptr}4 | | m_i1| +---| +--- (base class CBase2)8 | | {vfptr} 12 | | m_i2| +--- 16 | m_iChild+---CChild::$vftable@CBase1@:| &CChild_meta| 00 | &CChild::{dtor} 1 | &CChild::f1 2 | &CBase1::g1 CChild::$vftable@CBase2@:| -80 | &thunk: this-=8; goto CChild::{dtor} 1 | &CBase2::f2 2 | &CChild::g2CChild 分別從CBase1 和 CBase 中繼承一個vfptr.
-
菱形結構繼承的類(非虛繼承)
其類的布局如下:
class CGrandChild size(28):+---| +--- (base class CChild1)| | +--- (base class CBase)0 | | | {vfptr}4 | | | m_iBase| | +---8 | | m_iChild1| +---| +--- (base class CChild2)| | +--- (base class CBase) 12 | | | {vfptr} 16 | | | m_iBase| | +--- 20 | | m_iChild2| +--- 24 | m_iGrandChild+---CGrandChild::$vftable@CChild1@:| &CGrandChild_meta| 00 | &CGrandChild::{dtor} 1 | &CGrandChild::f0 2 | &CBase::g0 3 | &CGrandChild::h0 4 | &CGrandChild::h1 5 | &CGrandChild::h2 CGrandChild::$vftable@CChild2@:| -120 | &thunk: this-=12; goto CGrandChild::{dtor} 1 | &thunk: this-=12; goto CGrandChild::f0 2 | &CChild2::g0 3 | &thunk: this-=12; goto CGrandChild::h0這種繼承是有風險的,即通過CGrandChild 去訪問m_iBase 時,容易造成二義性,需要使用"pGrandChild->CChild::m_iBase" 這種方法去訪問。
為了避免這種問題,C++ 中有一種機制是虛繼承。
-
單一虛繼承
其類的布局如下:
class CChild1 size(24):+---0 | {vfptr}4 | {vbptr}8 | m_iChild1+--- 12 | (vtordisp for vbase CBase)+--- (virtual base CBase) 16 | {vfptr} 20 | m_iBase+---CChild1::$vftable@CChild1@:| &CChild1_meta| 00 | &CChild1::h1 CChild1::$vbtable@:0 | -41 | 12 (CChild1d(CChild1+4)CBase)CChild1::$vftable@CBase@:| -160 | &(vtordisp) CChild1::{dtor} 1 | &(vtordisp) CChild1::f0 2 | &CBase::g0 3 | &CBase::h0從布局中看,發現多了一個vbptr 指針,則是一個指向基類的虛基類指針;在派生類和虛基類之間又多了“vtordisp for vbase CBase”,vtordisp 并不是每個虛繼承的派生類都會生成的,關于這部分可以參考MSDN 中?vtordisp;在vtordisp 后面則是虛基類的一個拷貝。
-
多重繼承的類(虛繼承)
virtual public Child1, public CChild2
其類的布局如下:
class CGrandChild size(28):+---0 | {vfptr}| +--- (base class CChild2)4 | | m_iChild2| +---8 | {vbptr} 12 | m_iGrandChild+--- 16 | (vtordisp for vbase CChild1)+--- (virtual base CChild1) 20 | {vfptr} 24 | m_iChild1+---
public Child1, virtual public CChild2
其類的布局如下:
class CGrandChild size(20):+---| +--- (base class CChild1)0 | | {vfptr}4 | | m_iChild1| +---8 | {vbptr} 12 | m_iGrandChild+---+--- (virtual base CChild2) 16 | m_iChild2+---virtual?public Child1, virtual public CChild2
class CGrandChild size(28):+---0 | {vfptr}4 | {vbptr}8 | m_iGrandChild+--- 12 | (vtordisp for vbase CChild1)+--- (virtual base CChild1) 16 | {vfptr} 20 | m_iChild1+---+--- (virtual base CChild2) 24 | m_iChild2+---通過上述虛繼承的情況來看,可以看出有虛繼承的派生類中,派生類和虛基類的數據是完全隔開的,先存放派生類自己的虛函數指針,虛基類指針和數據;然后有vtordisp 作為間隔;在存放虛基類的內容。
-
菱形結構繼承的類(虛繼承)
其類的布局如下:
class CGrandChild size(40):+---| +--- (base class CChild1)0 | | {vfptr}4 | | {vbptr}8 | | m_iChild1| +---| +--- (base class CChild2) 12 | | {vfptr} 16 | | {vbptr} 20 | | m_iChild2| +--- 24 | m_iGrandChild+--- 28 | (vtordisp for vbase CBase)+--- (virtual base CBase) 32 | {vfptr} 36 | m_iBase+---有了上文的基礎,這個派生類的機構就不難理解了。
轉載于:https://www.cnblogs.com/jiaochen/p/5524335.html
總結
以上是生活随笔為你收集整理的C++ 中类的内存布局的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 同步互斥
- 下一篇: Docker 新网络 overlay