一個空的class:如
class X{} ;
sizeof(X)==1;
sizeof為什麼為1,他有一個隐晦的1 byte,那是被編譯器安插進去的一個char,這使得class2的兩個objects得以在記憶體中配置獨一無二的位址:
X a,b;
if(&a==&b) cerr<<"yipes!"<<endl;
class X{};
class Y:public virtual X{};
class Z:public virtual X{};
class A:public Y,public Z{};
cout<<"sizeof(X):"<<sizeof(X)<<endl;
cout<<"sizeof(Y):"<<sizeof(Y)<<endl;
cout<<"sizeof(Z):"<<sizeof(Z)<<endl;
cout<<"sizeof(A):"<<sizeof(A)<<endl;
我的vs結果是1 ,4,4,8.
但是讓人搞不懂的是Y、Z的大小。主要大小受三個因素的影響:
- 語言本身所造成的額外負擔,當語言支援虛基類virtual base class的時候,就導緻一個額外的負擔,這個一般反映在某種形式的指針身上,它或者指向virtual base class subobject,或者指向一個相關表格。
- 編譯器對于特殊情況所提供的優化處理,因為class X有1 byte的大小,這樣就出現在了class Y和class Z身上。這個主要視編譯器而定,比如某些存在這個1byte但是有些編譯器就将他忽略了(因為已經用虛指針了是以這個1byte就可以不用作為記憶體中的一個定位)。這種情況是對empty virtual base的特殊處理,如VS。
- Alignment的限制,就是所謂的對齊操作,比如你現在占用5bytes編譯器為了更有效率地在記憶體中存取就将其對齊為8byte。
- 下面說明在vs2010中的模型,因為有了虛指針後是以1byte就不用了,是以class Y和class Z的大小就是4bytes,如下圖:
現在你覺得class A的大小應該是多少呢?一個虛基類子對象隻會在派生類中存在一份實體,不管他在繼承體系中出現多少次,是以公用一個1byte的classX實體,再加上class Y和class Z這樣就有9bytes,如果有對齊的話就是12bytes但是vs2010中省略了那1byte是以就不存在對齊就直接是8bytes。謎底終于揭開了!!!
Data Member的綁定:the binding of a data member
Data Member的布局
類的static data member會放在程式的資料段(data segment)。
c++ standard要求,在同一個access section中,member的排列隻需符合”較晚出現的members在class object中有較高的位址“這一條件即可。也就是說,各個members并不一定得連續排列。什麼東西可能會介于被聲明的members之間呢?members的邊界調整(alignment)可能就需要填補一些bytes。
不同的access section的資料們放置沒有強制的前後關系。vptr的放置也沒有強制規定。
Data Member的存取
1)對于static data member,
每次程式取用static member,就會被内部轉換為對該唯一的extern實體的直接參考操作。存取static members并不需要通過class object。對于繼承而來的static member其存取路徑也是同樣直接。(因為static members隻存在唯一的一份實體)
(如何有2個classes,每一個都聲明了一個static member freelist;那麼都被放在程式的data segment時,就會導緻沖突,編譯器的解決辦法是暗中對每一個static data member編碼(name-mangling),以獲得一個獨一無二的程式識别代碼。任何name-mangline都有2個要點:
1.一種算法,推導出獨一無二的名稱
2.獨一無二的名稱可以輕易被推導回原來的名稱。
(2)對于nonstatic data member,
每一個nonstatic data member的偏移量offset,在編譯時期即可獲知。(派生自單一或多重繼承串鍊也一樣)。
而當虛繼承時,虛繼承将為“經由base class subobject 存取 class members”導入一層新的間接性。
nonstatic data member直接存放在class object之中,除非經由明确explicti或暗喻的implicit的class object,沒有辦法直接存取他們。隻要程式員在一個member function直接處理一個nonstatic data member,所謂的”implicit class object“就會發生。例如:
Point3D Point3D::translate(const Point3D &pt){
x+=pt.x;
y+=pt.y;
z+=pt.z;
}
表面上所看到的對于x,y,z的直接存取,事實上是經由一個”implicit class object“(有this指針表達)完成,事實上這個函數的參數是:
Point3D Point3D::translate(Point3d * const thisconst Point3D &pt){
this->x+=pt.x;
this-> y+=pt.y;
this->z+=pt.z;
欲對一個nonstatic data member進行存取操作,編譯器需要把class object的起始位址加上data member的偏移量(offset)。
舉個例子,如:
class Point3d{
public:
//..
private:
float x;
static List<Point#d*> *freeList;
float y;
static const int chunkSize=250;
float z;
Point3d orgin;
origin._y=0.0;
那麼位址&origin._y将等于
&origin+(&Point3d::_y-1);
請注意-1操作,指向data member的指針,其offset值總是被加上1,這樣可以是編譯系統區分出”一個指向data member的指針,用以指出class的第一個member”和“一個指向data member的指針,沒有指出任何member”兩種情況。
每一個nonstatic data member的偏移量(offset)在編譯時期即可貨值,甚至一個member屬于一個base class subobject(派生自單一或多重繼承串鍊)也是一樣,是以,存放一個nonstatic data member,其效率和存取一個c struct member或一個nondervied class的member是一樣的。
必須通過對象才能通路nonstatic data member(要不然通路的是誰的data member呢)。方法為對象的位址加上data member的offset就是這個data member的位址。
但在有虛拟繼承的情況下,由于“經由base class subject存取class member”導入一層新的間接性,通路的時候,會有不同。考慮如下代碼:
Point3d origin, *pt;
origin.x = 0;
pt->x;
從origin和pt存取有何差異?
答:當Point3d是一個derived class,而在其繼承結構中有一個virtual base class,并且被存取的member是一個從該virtual base class繼承而來的member時,有差異。
從pt存取,這時我們不知道pt指向哪一種class type,即無法知道編譯時期這個member真正的offset位置,這個存取操作必須延遲至執行期,經由一個額外的間接引導,才能夠解決。存取速度比較慢一些。從origin存取,origin的類型是明确的,members的offset位置在編譯時期就固定了
繼承與Data Member
在c++繼承模型中,一個dervied class object所表現的東西,是其自己的member是加上其base class member的總和。在大部分編譯器中,base class member總是先出現,但屬于virtual base class的除外(一般而言,任何規則一旦碰上virtual base class就沒轍了,這裡也不例外)。