一、封裝模型的內存布局
常見類對象的成員可能包含以下元素:內建類型、指針、引用、組合對象、虛函數。
另一個角度的分類:
數據成員:
成員函數:
類中的內建類型按照聲明的順序在內存中連續存儲,並且分配的大小由內建類型本身的大小決定(依賴機器),布局受字節對齊影響(本篇不討論字節對齊)。
&* (*
存儲方式同1的場合,不同點為指針和引用通常為固定大小(32位機器4字節、64位機器8字節)。
有關引用:個人理解的引用就是懶人專用指針,取地址又間地址是很麻煩的操作,於是出現了自動取址又間址的指向常量的常指針。
在類中聲明可以測出固定字節大小,所以也是占用固定的字節大小。
內存布局圖示(本篇以及後續篇使用的環境為 32位Win7, VS2008):
再來看一下地址:
結論:(顯而易見就不解釋了)
類對象最終被解釋成內建類型,布局依然按照聲明的順序,並且對象布局在內存中依然是連續的
內存布局圖示
通過程序輸出看一下
typedef (*<<<<(*)&t<<<<<<(*)*(*)&t<<<<= (PF)*(*)*(*)&<<= (PF)*(*)*(*)&
輸出圖示
推理證明:
1.取t的地址強轉成(int*)類型輸出以後得到的地址 == 取t的vfptr的地址(調試窗口第一行): 虛函數指針被放在對象布局的首地址位置
2.因為(int*)&t == vfptr,那麼*vfptr得到的是虛函數表的首地址。
(int*)*vfptr,把虛函數表的首地址強轉成(int*)的地址 == t對象的__vftable的虛函數表的地址(調試窗口第四行行):虛函數指針指向虛函數表
3.vftable的首地址到vftable的第一個函數的地址中間相差很多空間:虛函數表還承擔了虛函數以外的內容
什麼內容也會放在虛函數表中呢?
虛函數表用來實現多態,多態意味著類型上的模糊,模糊以後必須有東西來記錄自己的老本,否則無法實現另外一個東西——RTTI。
結論:
在包含虛函數的場合多了一個vfptr,它是一個const指針,位於類布局中的首位置,指向了虛函數表,虛函數表包含了虛函數地址,通過虛函數地址訪問虛函數。
並且虛函數表的首地址存在了本類的類型信息,用於實現RTTI。
static的特性眾所周知,從調試窗口觀察變量並不能得出什麼結論,我們先列出幾條特性:
1.static成員為整個類共有的屬性
2.static函數不包含this指針
3.static成員不能訪問nonstatic成員
初步結論:
內存對象模型中對static作了隔離處理(不是所有對象具有的),static自己獨霸一方。
通過以上5條現在來構建C++的封裝模型:
有關普通的成員函數
所謂類,就是自己圈定了一個域名,所以在內存中的代碼區也圈定了自己的域,普通的成員函數放在那裡。
有關靜態成員函數
在代碼區中圈定的類域名中的圈定一個static區域,思路依然是獨霸一方。
有關構造函數
由於構造函數的特殊性,所以在代碼區擁有一個自己的構造代碼區域。
現在又有了一個更完整的模型:
假定讀者已經了解堆/棧/靜態區和常量區/代碼區
根據上圖我們得到一些結論
1.類最終被解釋內建類型(內建類型過了編譯期以後,都不復存在,只是編譯期的解讀方式而已)
2.內建類型按照聲明的次序順序存儲
3.存在虛函數的場合,會生成vfptr,並且vfptr->vtable->function()
4.靜態成員被單獨對待、數據只有一份拷貝,函數被放到static區域。
5.Type Infomation被放到vftable中
二、封裝模型的構造過程
1.靜態是編譯期決定的,所有對象共有的數據拷貝,優先創建。
2.進入構造函數,優先創建vfptr和vftable,也就是優先構造虛函數部分
3.其次按照聲明的順序構造數據成員。
我們可以使用逗號表達式來干一些有意思的事情。
事先我們需要定義
(*PF)();
PF pf = NULL;
<<, )), a((cout<<, fun(){cout<<<<=(PF)*(*)*(*), pf()), cout<<, )), data2((cout<<, fun(){cout<<<< T::sdata1 = (cout<<, );
以下是程序運行的結果:
靜態--虛函數表--聲明次序初始化。
文章不免有疏忽和不足的地方,歡迎大家批評指正。郵箱【[email protected]】
下一篇重點講繼承時和多態時的內存布局。