class x ;
class y : public virtual x ;
class z : public virtual x ;
class a : public y, public z ;
它們的sizeof結果如下:
sizeof(x) = 1;
sizeof(y) = 8; //視編譯器不同而不同
sizeof(z) = 8; //視編譯器不同而不同
sizeof(a) = 12;
實際上,class x 並不是空,它有乙個晦澀的1bytes,那是被編譯器安插進去的乙個char, 這使得這個class的兩個物件將在記憶體中有不同的位址。
而class y 和 class z 與三個因素有關。
一:語言本身造成的額外負擔。當語言支援virtual base class 時,會導致一些額外的負擔。在派生類中,這個額外負擔反映在指標身上,它或者是指向virtual base class subobject;或者指向乙個**,**中存放的若不是virtual base class subobject的位址,就是其偏移量。
二:編譯器對於特殊情況所提供的優化處理。virtual base class x 的subobject的1bytes大小也出現在class y 和 z 身上。傳統上它被放在派生類固定部分的尾端。而某些編譯器會對empty virtual base class 提供特殊支援。
三:alignment 的限制。class y 和 z 目前為5位元組,為了進行對齊,將會被變成8位元組。
而在某些編譯器中,比如visual c++中,結果為1,4,4,8。這是因為編譯器提供了特殊處理。它的模型如下:
對於class a,它的大小是多少呢?由以下決定(乙個虛擬繼承的基類只有乙個實體,不管被繼承了多少份)
二:base y 的大小,為4位元組。z也一樣。
三:class a 自己的大小0位元組。
四:alignment的情況。這個時候為9位元組,為了對齊,所以為12位元組。
而visual c++進行特殊處理後,base y + base z 的大小為8位元組。
origin. chunksize = 250 等價於 point3d::chunksize = 250。
pt->chunksize = 250 等價於 point3d::chunksize = 250。
在c++中,這是「通過乙個指標和通過乙個物件來訪問資料成員時,結論完全相同」的唯一一種情況。因為此時的member並不在物件中。
point3d origin, *pt = &origin;
origin.x = 0.0
pt->x = 0.0
"從origin訪問「和」從pt訪問'有什麼重大的差異嗎? 答案是「當point3d是個派生類,而x資料成員是基類的資料成員時,就有重大的差異。對於指標訪問資料而言,這個操作必須延遲到執行時期進行訪問。而如果使用origin,則在編譯時期就確定了。
class concrete
private:
int val;
char c1;
char c2;
char c3;
sizeof(concrete) = sizeof(val) + sizeof(char)*3 + alignment = 8;
class concrete1
private:
int val;
char c1;
class concrete2:public concrete1
private:
char c2;
class concrete3: public concrete2
private:
char c3;
這個時候,sizeof(concrete3)並不等於8。
sizeof(concrete3) = sizeof(concrete1) + sizeof(concrete2) + sizeof(concrete3) = 8 + 4 + 4 = 16。
因為虛擬繼承只有乙個基類,如何對虛擬繼承的基類進行布局呢?一種方法是把派生類分成兩個部分:乙個不變區域性和乙個共享區域性。
一般的布局策略是先安排好derived class 的不變部分,然後再建立其共享部分。那麼如何訪問class的共享部分呢?cfront 編譯器會在每乙個派生類物件中安插一些指標,每個指標指向乙個virtual base class。這有兩個問題:
一:每乙個物件必須針對其每乙個虛基類揹負乙個額外的指標。
二:由於虛擬繼承串鏈的加長,導致間接訪問層次的增加。這裡的意思是:如果我有三層虛擬,我就需要三次間接訪問。
metware和其它編譯器對於第二個問題的解決方法是:複製巢狀的虛擬類的指標放到派生類物件中,雖然付出了一些空間上的代價,但是訪問時間不會隨著繼承的增加而增加時間。
至於第乙個問題,一般而言有兩個解決方法。microsoft編譯器引入所謂的virtual base class table。virtual base class指標放在這個**中。第二個解決方法是:是在virtual function table中放置virtual base class的offset。
class point3d
public:
virtual ~point3d();
static point3d origin;
float x, y, z;
那麼& point3d::z; 得到的是什麼? 將得到z 在類物件中的偏移量。如果vptr放在物件的起頭,則三個座標值在物件布局中的offset分別是4, 8 , 12。然而我們若去取data members的位址時, 傳回的值應該是多1的, 也就是1, 5, 9。因此:
printf("%p/n", &point3d::x);
printf("%p/n", &point3d::y);
printf("%p/n", &point3d::z);
在vc6.0中編譯得到的結果是:
0000000c
在bcb3中編譯得到的結果是:
0000000d
這說明vptr放在編譯器的起頭。vc6.0得到的結果可能進行了特殊處理。好!現在回到正題,為什麼值要加1呢?看下面這個例子。
float point3d::*p1 = 0;
float point3d::*p2 = &point3d::x;
if( p1 == p2 )
cout << "p1 & p2 contain the same value --";
cout << " they must address the same member!" << endl;
為了區分p1和p2, 每乙個真正的member offset值都被加上1。 因此,不論編譯器或使用者都必須記住,在真正使用該值以指出乙個member之前,請先減掉1。
為了測試vc6.0的值為什麼沒有加1,我測試程式如下:
class point3d
public:
static point3d origin;
float x, y, z;
int main()
float point3d::*p1 = 0;
float point3d::*p2 = &point3d::x;
if( p1 == p2 )
cout << "p1 & p2 contain the same value --";
cout << " they must address the same member!" << endl;
return 0;
發現p1的值為0xffffffff, p2的值為0x00000000。不執行輸出。
if( p1 == null )
cout << "p1 & p2 contain the same value --";
cout << " they must address the same member!" << endl;
p1的值為0xffffffff, 執行輸出。
但是null的值為0啊!為什麼會執行輸出呢?
我的猜測是vc6.0對指標的值還是加1了,列印出來的結果是減1後的。
因此:& point3d::z; 和 & origin.z之間的差異,就非常明確了。前者取」它在類中的偏移量,而後者取類物件中z的真正的位址「。
深度探索C 物件模型學習筆記 Data語意學
例子 class x class y public virtual x class z public virtual x class a public y,public z 物件大小由下述原因決定 1.語言本身所造成的額外負擔 如 支援virtual base classes時,derived cl...
深度探索C 物件模型之Data語意學讀書筆記
3.4繼承與data member 測試原始碼 class concrete1 class concrete2 public concrete1 class concrete3 public concrete2 對於此例子,使用vc 編譯,程式執行結果與書中討論相符,sizeof concrete3...
深度探索C 物件模型 語意學
有三種情況,會以乙個object的內容作為另乙個class object的初值 cpp class x x x x xx x 情況1,賦值物件 extern void foo x x void bar x foo bar default memberwise initalization 如果clas...