深度探索C 物件模型 第三章 Data 語意學

2022-09-03 01:21:07 字數 2727 閱讀 3385

乙個有趣的問題:下列 類 sizeof大小

class x{}    //1 

class y:public virtual x{} //4 or 8

class z:public virtual x{} // 4 or 8

class a:public y,public z{} // 8 or 12

主要原因:為了保持每乙個類生成物件在記憶體中的唯一性,編譯器必須要給空類生成乙個char來維持object的唯一性;

而virtual繼承中,僅保持了base class的指標,有些編譯器會繼承base的乙個char資料,有些編譯器會捨去。如果保持,則包含資料,且為5bytes,需要保持alignment,則為8bytes。class a,同時繼承y和z。// visual c++和g++採取empty virtual base class被視為derived class object最開頭的一部分,則derived class不需要耗費1char 來維持object的獨一性。

g++ 上結果為 1,8,8,16 (由於sizeof(int *) = 8) 

sizeof(y) = sizeof(pointer in machine)

class w sizeof(w) = 1;w w ; sizeof(w) = 1; 表示實際占用的大小,(針對alignment編譯器已經做好了優化,不用考慮)

c++物件模型盡量以空間優化和訪問速度的考慮來實現nonstatic data members。資料直接存放在每乙個class object中。

class object 大小:

1) 實際的資料物件 non-static data member

2) 編譯器自動加上的額外data member,用於支援語言特性(主要是virtual)

3) 因為alignment的需要(這個目前來說由底層實現,一般不用考慮,sizeof得到的是實際大小)

3.1 data member 的繫結

有意思的一章節,雖然了class資料繫結的發展

extern float x;

typedef int type;

class a private: float x;typedef float type;} 

//丟擲乙個問題,函式x返回值是全域性的x還是class內部的x?

當然,現在的c++模型肯定是內部的;而之前不一定,從前向後解析,並進行繫結;所以x被繫結到了全域性。 

現在進行了改進:只有當把class掃瞄完之後才對函式本體進行解析。並且遇到新的型別,就標記前乙個標記為非法(如float x覆蓋int x),用正確的解析。但對於引數,但對於引數仍可能繫結到全域性變數(如type val,仍繫結到全域性)。

因此我們仍需要採用防禦性程式風格: 保證變數宣告在定義之前,來確保非直覺繫結的正確性。

1) 總是把宣告放到class的最開頭

2)總是把inline function放在class體外部;

3.2 data member 的布局(data member layout)

由編譯器確定,一般規則相同的access section的資料放在同乙個區域,也可以多個section放在同乙個區域。

3.3 資料的訪問

丟擲乙個問題:origin.x和pt->x 訪問有什麼區別? origin.x 主要是編譯期間靜態繫結,根據x的offset偏移量進行訪問;而pt可能會不確定型別,需要執行時確定;

主要區別靜態資料和非靜態資料,靜態資料儲存在data segment段,可以被類直接訪問,所有繼承的類也都共享該靜態物件,在記憶體中是唯一性;

為了避免多個靜態變數的命名衝突,可以採用name-mangling才進行對映,保證唯一性,並可以反推回來。

非靜態資料位只有object才可以訪問,預設訪問時需要有this(implicit class object)

有乙個問題:本文中指出乙個指向data member的指標,用以指出class的第乙個member,和乙個指向data member的指標,兩者之間相差乙個byte,類似於空class一樣。但我在g++上測試,兩者的基位址保持一致,並無1byte的相差;

包含virtual function的布局:有的vptr放在資料尾部,保持和struct相容性;但目前普遍放在class的頭部,便於訪問virtual function。

3.4 繼承與data member

單一繼承,多重繼承(不包含虛擬繼承):按照繼承class宣告的順序繼承資料,當然繼承順序主要由編譯器決定;

書中提到:多重繼承中可能由於alignment導致多重繼承之後class的size變大。但似乎在g++中,能夠有效的優化,沒有帶來alignment的開銷。(經測試)

主要難點是virtual inheritance,虛擬繼承主要用來保持資料的唯一性,但也帶來了複雜性和效率問題。不同的編譯器有不同的處理方式。這裡提到兩種方法:

1)子類儲存對虛擬繼承父類的指標,隨著虛擬繼承巢狀層數的增多,會導致間接訪問時間開銷過大,需要多層間接訪問;

2)為了避免上述情況,保持訪問的一致性,將virtual function entries 和virtual base class offset放在一起;可以將虛擬繼承的父類物件拷貝到自己的物件空間,同時保持對該空間的指標;

如果同時繼承來自多個虛擬class,則僅保留乙份對原始父類; (指向父類的指標放在class空間的開頭) 【補圖】

3.5 和 3.6 主要談到效率問題和指向members指標

作者測試的環境和現在的不太一樣,下一步在**。

《深度探索C 物件模型》第三章讀書筆記

看之前的疑問 1.既然基類先於派生類初始化,基類的資料成員與派生類的資料成員是如何放置的?筆記 1.當初始化乙個類時,如果裡面沒有任何成員,那麼大小為1,編譯器這麼做的原因是為了讓每乙個物件都能保證獲得獨一無二的記憶體位址 2.在某些編譯器上,當類中沒有資料成員,但是卻存在虛函式時,那麼一些編譯器 ...

《深度探索C 物件模型》第三章 Data語意學

首先給出以下例子 y,z的記憶體大小受三個因素的影響 1.語言本身造成的額外負擔,指的是虛繼承中在類中會產生乙個指向虛表的指標,這個指標在32位的系統下是4個位元組。2.編譯器對特殊情況提供的優化處理。3.alignment限制。class a的大小由以下幾點決定 1.被共享的 class x,為1...

《深入探索C 物件模型》第三章奇怪語句解釋

在第三章的3.3節有下面這樣一段描述 class point3d point3d origin origin.y 0.0 origin point3d y 1 對於 1 的操作解釋是這樣的 指向data member的指標,其offset值總是被加上1,這樣可以使編譯系統區分出 乙個指向data m...