天天看點

《深度探索c++對象模型》chapter3 Data語意學

一個空的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,如下圖:
《深度探索c++對象模型》chapter3 Data語意學

  現在你覺得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就沒轍了,這裡也不例外)。

繼續閱讀