在學習完類物件的構造後,下面就需要學習類資料成員和函式成員的訪問。
編譯器對於類物件的處理方式:(1)對於空類,編譯器為該類新增乙個char型別的成員,用來唯一標識該類在記憶體的位置(2)使用對齊機制,當乙個類的記憶體位元組數不足4的倍數將自動補充,目的是為了定址的方便
有些編譯器對於空類的處理進行了優化處理,僅當該空類被繼承的時候,空類物件在子類物件中不占用任何記憶體,單獨空類的大小仍是1個位元組,這樣可能會避免對齊機制,優化了c++物件模型的記憶體空間。
但是對於非空類,不處理和優化處理對於類物件的記憶體空間沒有任何改變。
例如:class a{};
class x:public virtual a{};
class y:public virtual b{};
class z:public x,public y{};
兩種處理機制得到的各個類的大小:
未優化 優化後
1 1
8 4
8 4
12 8
一、資料成員深度探索
1、資料成員在類中宣告的位置盡量靠前,雖然c++標準沒有規定宣告順序,但是為了防範全域性變數和區域性成員變數產生二義性,所以盡量在宣告函式前宣告全部變數。
inline函式在未定義之前不去去決斷其中使用的變數,但若宣告之後立即定義,則會判斷函式前宣告的變數。
2、nostatic資料成員是在物件儲存空間中存放的,static資料成員存在在靜態儲存區中,每個類只用乙份例項(除了模板類)。nostatic資料成員在記憶體中的布局要求是:在同一訪問許可權段(public private protected)中,晚宣告的成員的位址比早宣告的成員的位址高即可,不要求相鄰。
3、編譯器為了實現某種機制為類物件新增的成員,如vptr指標,它的存放位置c++標準並沒有限制,可以放在物件首部,也可以放在尾部。放在首部方便了對虛函式呼叫,放在尾部可以與c語言中的結構體相相容,各有好處,視編譯器而定。
4、資料成員的訪問
考慮通過物件訪問成員和通過物件指標訪問成員有什麼區別?
(1)static資料成員的訪問
由於static資料成員儲存在程式的靜態儲存區中,當通過類物件、物件指標或者類::方式訪問該成員是,編譯器將內部轉化為對靜態變數的訪問。因為static資料成員並不儲存在類物件空間中,所以對靜態成員的訪問不需要經過物件,因此通過物件和指標訪問static變數沒有任何差異。
若取static資料成員的位址,將會得到該成員在記憶體中的實際位址,而且其指標型別和普通指標型別是相同的,而nostatic資料成員則有所區別。
static int a;為乙個a類中的乙個static成員,宣告乙個指向它的指標應該這樣宣告:int *p=&a::a;指標的使用也和普通指標相同。
(2)nostatic資料成員的訪問
nostatic資料成員的訪問必須通過物件或指向物件的指標,因為nostatic資料成員的位址依賴於物件的儲存位址,編譯器會在訪問nostatic成員時加上this指標(指向物件的起始位址),通過this指標和nostatic成員在物件中的offset值訪問該成員。
換句話說,nostatic資料成員的實體地址可表示為this+offset。
所以,取某個類中的nostatic資料成員位址得到的是該成員在物件中儲存offset,即&a::b轉化成指標型別就是int a::*p=&a::b;想要使用p還需要通過物件才能完成如a.*p==a.b;
注意:&a::b的到的值在編譯器端將會自動加1,也就是說真實的offset=&a::b-1,這樣做的目的是為了區別空的指向資料成員指標(0)和非空指向資料成員指標。當呼叫指標的時候首先將指標值減1,如果是空的成員指標的話將不能呼叫。這樣就可以把空指標(0)和指向首部成員的指標(0)分開。
例如int a::*p1=0;int a::*p2=&a::b;則p2的值是1(假設b放在物件首部,vptr在尾部),當呼叫時想將p2-1+this獲取成員位址,空指標則是-1。
1)單一繼承(非虛擬繼承):子類總是把基類物件放在子類物件的首部,然後才放子類自己的成員。因此,子類通過物件或者通過物件指標訪問基類成員不會存在間接性,基類成員在編譯期就可以確定其offset值(基類成員在基類中的offset值和在子類中的offset值是一樣的)。因為基類物件在子類物件的首部,這樣當基類指標被子類賦值時,基類指標仍然指向基類物件起始位址。
當存在多型時,編譯器會自動根據vptr的位置修改資料成員的offset值。
2)多重繼承(非虛擬繼承):此種情況較上述情況麻煩,需要編譯器進行位址轉換。基類按照繼承生命的順序在繼承類中排列,因此第乙個基類的位址不需要轉化,直接複製即可。而處於中間的基類的位址就需要通過this+中間類的大小才能夠確定,這個工作由編譯器完成。位址轉化的時候首先判斷子類位址是否為零。
這種情況下,訪問基類物件中的成員在編譯器是就確定了offset值,因此通過物件和指標訪問基類物件不會存在差異。
3)虛擬繼承:虛擬繼承使得繼承類無論繼承多少個虛擬基類,都會只包含乙個虛擬基類物件。那麼,虛擬基類物件在繼承類物件記憶體分布中就只存在乙個基類物件例項。現在通用的是將虛擬基類物件放在繼承類物件的尾部,繼承類其他成員在虛擬基類物件上面。
那麼,虛擬基類物件在每個繼承類中的offset值是不同的,因此如果通過物件指標訪問基類物件成員,不能在編譯期確定基類成員的offset值。但是,通過物件訪問基類成員時是在編譯期確定offset值。
比如:vertex3d v3d;
point3d *p=&v3d;
如果還按照之前的offset值,_x在point3d中的offset是13,但是_x在vertex3d中offset不是13,這樣就可能提取錯誤的值,因此,對虛擬基類物件成員的提取將通過虛表間接獲得基類物件的位址進行轉換。
在虛擬繼承時,使用物件和物件指標訪問基類物件成員會產生差異,使用物件指標訪問基類物件成員時,必須等到執行期進行判斷指標指向的真正型別才能確定offset值。
深度探索C 物件模型
傳世經典書叢 深度探索c 物件模型 美 stanley b.lippman 斯坦利 b.李普曼 著 侯捷 譯 isbn978 7 121 14952 8 2012年1月出版 定價 69.00元 16開 356頁 宣傳語 如果你是一位c 程式設計師,渴望對於底層知識獲得乙個完整的了解,那麼本書正適合你...
深度探索C 物件模型
傳世經典書叢 深度探索c 物件模型 美 stanley b.lippman 斯坦利 b.李普曼 著 侯捷譯 isbn978 7 121 14952 8 2012年1月出版 定價 69.00元 16開 356頁 宣傳語 如果你是一位c 程式設計師,渴望對於底層知識獲得乙個完整的了解,那麼本書正適合你 ...
深度探索C 物件模型
深度探索c 物件模型 本書目錄結構如下 第1章 關於物件 object lessons 加上封裝後的布局成本 layout costs for adding encapsulation 1.1 c 模式模式 the c object model 簡單物件模型 a object model 驅動物件模...