生活随笔
收集整理的這篇文章主要介紹了
C++ 多重继承之内存存储
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
C++ 之多重繼承
1. C++中class與struct。
在C++里面,class與struct沒有本質的區別,只是class的默認權限是private,而struct則是public。這個概念也揭示了一點:class和struct在內部存儲結構上是一致的。所以我們可以利用這一點來探討class的實現原理。我們可以將class轉換成對應的struct對象,通過struct的簡單性來展示class的內存存儲結構。
2. 關于class的基本內存結構
class包括成員變量和成員函數。對于成員變量,其結構和struct的結構是一致的,即按照聲明的順序,安排每個成員的內存位置。對于成員函數,如果是非虛函數(包括普通函數和靜態函數),他們實際上同其他函數沒有區別。對于非靜態非虛函數,默認隱藏了this參數。當編譯時,編譯器將函數地址直接編譯進去。因此,這類函數沒有動態能力。對于虛函數,其函數地址將存在類this指針關聯的虛函數內(參見上一篇文章),在運行時,從虛函數表內取得地址后再調用。
這樣一個不帶虛函數的類的內存結構,等同與一個類似的struct,而帶虛函數的類的內存結構,等同于一個帶有虛函數指針的struct。我用偽代碼可以清晰的表示出來:
[cpp]?view plaincopy
class?ClassA?{?? ???int?a;?? ???flaot?b;?? ???void?*c;?? };?? ?? struct?StructA?{?? ???int?a;?? ???float?b;?? ???void?*c;?? <span?style="font-family:Arial,?Helvetica,?sans-serif;">};?? ?? ?? b=0x7fffad613130,?pa=0x7fffad613138??
class ClassAWithVirtual { int a; float b; void* c; virtual ~ClassAWithVirtual();};//等同于struct StructAWithVirtual { VTable *vtable; //包括虛函數表的vtable int a; float b; void *c;};
3. class的單線繼承
單線繼承是很簡單也很容易理解的一種繼承方式。如果有class B繼承自class A。那么,在class B的低地址部分,是class A的成員空間。這樣class B可以直接轉換為class A。
如果class A有虛函數,那么class B必須也有虛函數表。那么,如果class A沒有虛函數,而class B卻有虛函數,那么這個時候的內存分布應該是什么樣呢?class B還能否直接轉換為class A呢?
[cpp]?view plaincopy
#include?<stdio.h>?? #include?<stddef.h>?? ?? #define?OFFSET(X,m)?(offsetof(X,?m))?? ?? class?A?{?? public:?? ????int?a;?? };?? ?? class?B?:?public?A{?? public:?? ????int?b;?? ????virtual?~B()?{?}?? };?? ?? int?main()?{?? ????printf("offset?A::a=%d,?offset?B::a=%d,?offset?B::b=%d\n",?OFFSET(A,a),?OFFSET(B,?a),?OFFSET(B,?b));?? ?? ????B?b;?? ????A?*pa?=?&b;?? ????printf("b=%p,?pa=%p\n",?&b,?pa);?? ?? ????return?0;?? }??
在G++ 4.6 ubuntu 10.04下,輸出的結果是
[cpp]?view plaincopy
offset?A::a=0,?offset?B::a=8,?offset?B::b=12?? b=0x7fffad613130,?pa=0x7fffad613138??
很明顯的可以看到,當B有虛函數時,B必須在內存的開始處添加一個8字節(64位系統)的虛函數表指針。這個時候,在class B內,A的成員變量不能從偏移為0的位置開始了。
3. 不帶虛函數的C++的多重繼承類的內存分布
一般情況下,如果一個類繼承自兩個類以上,那么,它的內存分布會像壘磚頭那樣一層一層的添加上去。比如
[cpp]?view plaincopy
#include?<stdio.h>?? #include?<stddef.h>?? ?? ?? class?A?{?? public:?? ????int?a;?? };?? ?? ?? class?B?{?? public:?? ????int?b;?? };?? ?? ?? class?C?:?public?A,?public?B{?? public:?? ????int?c;?? };?? ?? ?? int?main()?{?? ????printf("Offset:?C::a=%d,?C::b=%d,?C::c=%d\n",?offsetof(C,?a),?offsetof(C,?b),?offsetof(C,?c));?? ????C?c;?? ????A?*pa?=?&c;?? ????B?*pb?=?&c;?? ?? ?? ????printf("c=%p,?pa=%p,?pb=%p\n",?&c,?pa,?pb);?? ?????? ????return?0;?? };??
輸出結果是
[cpp]?view plaincopy
Offset:?C::a=0,?C::b=4,?C::c=8?? c=0x7fffc90bbab0,?pa=0x7fffc90bbab0,?pb=0x7fffc90bbab4??
這個與預想的很相似,它的等價結構是
[cpp]?view plaincopy
struct?C?{?? ???struct?A?a;?? ???struct?B?b;?? ???int?c;?? };??
的確像磚頭一樣,先放A,在放B,最后C的成員放上去。
那么,再考慮一種情況,如果是這樣一種繼承方式:
? ? ? ? ? ? ? ? ? ? ? ? ?A
? ? ? ? ? ? ? ? ? ? ? / ? ? \ ? ? ??
? ? ? ? ? ? ? ? ? ? B ? ? ?C
? ? ? ? ? ? ? ? ? ? ?\ ? ? ?/
? ? ? ? ? ? ? ? ? ? ? ?D
那么A的成員在D內部是一份還是兩份?
[cpp]?view plaincopy
#include?<stdio.h>?? #include?<stddef.h>?? ?? class?A?{?? public:?? ????int?a;?? };?? ?? class?B?:?public?A{?? public:?? ????int?b;?? };?? ?? class?C?:?public?A?{?? public:?? ????int?c;?? };?? ?? class?D?:?public?B,?public?C?{?? public:?? ???int?d;?? };?? ?? int?main()?{?? ????printf("Offset:?D::a=%d,?D::b=%d,?D::c=%d,?D::d\n",?offsetof(D,?B::a),?offsetof(D,?b),?offsetof(D,?c),?offsetof(D,?d));?? ????D?d;?? ????B?*pb?=?&d;?? ????C?*pc?=?&d;?? ????A?*pa_b?=?(A*)pb;?? ????A?*pa_c?=?(A*)pc;?? ?? ????printf("d=%p,?pa_b=%p,?pa_c=%p,?pb=%p,?pc=%p,?d.B::a=%p,?d.C::a=%p\n",?&d,?pa_b,?pa_c,?pb,?pc,?&d.B::a,?&d.C::a);?? ?????? ????return?0;?? };??
先看一下輸出結果:
[cpp]?view plaincopy
Offset:?D::a=0,?D::b=4,?D::c=12,?D::d?? d=0x7fffad730f30,?pa_b=0x7fffad730f30,?pa_c=0x7fffad730f38,?pb=0x7fffad730f30,?pc=0x7fffad730f38,?d.B::a=0x7fffad730f30,?d.C::a=0x7fffad730f38??
事實上,如果我直接寫b.a是錯誤的,因為編譯器不知到應該選擇那個a。同樣的,如果寫A *pa = &d也是錯誤的。
結合輸出結果,class D內部仍然等同于
[cpp]?view plaincopy
struct?D?{?? ???struct?B?b;?? ???struct?C?c;?? ???int?d;?? };??
而且存在兩份A,這兩份A分別包含在B和C內部。在使用時,必須正確指出是那個。
?
由此可見,不管繼承層次有多深,C++總是按照這種壘磚頭的方式疊加。如果有祖先類內部有重復包含,那么,C++也會重復包含相同的內容。
這也提醒我們,多重繼承不能太復雜,否則就很難搞清楚其結構關系了。
4. 帶虛函數的類的多重繼承的內存分布
帶虛函數的情況下,情況會變得非常復雜。首先,對于最簡單的一種繼承方式
? ? ? ? ? ? ? ? ? ? ? ? ?A ? ? ?B
? ? ? ? ? ? ? ? ? ? ? ? ? ? \ ? /
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?C
我們需要分好幾種情況來考慮:
? ?1、A B虛 C非虛
? ?2、A B 非虛,C虛
? ?3、A B 其中一個虛,C虛
? ?4、A B C 都虛
4.1 A B 虛 C非虛
如果只有A虛, 按照默認的規則,A的內存會被安排在偏移0處。這個時候,A的虛函數表也就是C的虛函數表。
如果只有B虛,因為B的內存會被安排在A之后,那么,B的虛函數表應該在B所在位置,C沒有虛函數表。
4.2 A B 不虛,C虛
這種情況下,虛函數表應該在偏移0處,然后才是A和B的內存結構。我們來驗證一下:
總結
以上是生活随笔為你收集整理的C++ 多重继承之内存存储的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。