帶虛函式的類的物件布局(1)
如果類中存在虛函式時,情況會怎樣呢?我們知道當乙個類中有虛函式時,編譯器會為該類產生乙個虛函式表,並在它的每乙個物件中插入乙個指向該虛函式表的指標,通常這個指標是插在物件的起始位置。所謂的虛函式表實際就是乙個指標陣列,其中的指標指向真正的函式起始位址。我們來驗證一下,定義乙個無成員變數的類c040,內含乙個虛函式。
struct c040
};執行如下**列印它的大小及物件中的內容。
print_size_detail(c040)
結果為:
the size of c040 is 4
the detail of c040 is 40 b4 45 00
果然它的大小為4位元組,即含有乙個指標,指標指向的位址為0x0045b440。
同樣再定義乙個空類c050,派生自類c040。
struct c050 : c040
;由於虛函式會被繼承,且維持為虛函式。那麼類c050的物件中同樣應該含有乙個指向c050的虛函式表的指標。
執行如下**列印它的大小及物件中的內容。
print_size_detail(c050)
結果為:
the size of c050 is 4
the detail of c050 is 44 b4 45 00
果然它的大小也為4位元組,即含有乙個指向虛函式表(後稱虛表)的指標(後稱虛表指標)。
虛表是類級別的,類的所有物件共享同乙個虛表。我們可以生成類c040的兩個物件,然後通過觀察物件的位址、虛表指標位址、虛表位址、及虛表中的條目的值(即所指向的函式位址)來進行驗證。
執行如下**:
c040 obj1, obj2;
print_vtable_item(obj1, 0, 0)
print_vtable_item(obj2, 0, 0)
結果如下:
obj1 : objadr:0012fdc4 vpadr:0012fdc4 vtadr:0045b440 vtival(0):0041d834
obj2 : objadr:0012fdb8 vpadr:0012fdb8 vtadr:0045b440 vtival(0):0041d834
(注:第一列為物件名,第二列(objadr)為物件的記憶體位址,第三列(vpadr)為虛表指標位址,第四列(vtadr)為虛表的位址,第五列(vtival(n))為虛表中的條目的值,n為條目的索引,從0開始。後同)
果然物件位址不同,虛表指標(vpadr)位於物件的起始位置,所以它的位址和物件相同。兩個物件的虛表指標指向的是同乙個虛表,因此(vtadr)的值相同,虛表中的第一條目(vtival(0))的值當然也一樣。
接下來,我們再觀察類c040和從它派生的類c050的物件,這兩個類各有自己的虛表,但由於c050沒有重寫繼承自c040的虛函式,所以它們的虛表中的條目的值,即指向的虛函式的位址應該是一樣的。
執行如下**:
c040 c040;
c050 c050;
print_vtable_item(c040, 0, 0)
print_vtable_item(c050, 0, 0)
結果為:
c040 : objadr:0012fd4c vpadr:0012fd4c vtadr:0045b448 vtival(0):0041d834
c050 : objadr:0012fd40 vpadr:0012fd40 vtadr:0045b44c vtival(0):0041d834
果然這次我們可以看到雖然前幾列皆不相同,但最後一列的值相同。即它們共享同乙個虛函式。
定義乙個c043類,包含兩個虛函式。再定義乙個c071類,從c043派生,並重寫繼承的第乙個虛函式。
struct c043
virtual void foo2() {}
};struct c071 : c043
};我們可以預料到,c043和c071各有乙個包含兩個條目的虛表,由於c071派生自c043,並且重寫了第乙個虛函式。那麼這兩個類的虛表的第乙個條目值是不同的,而第二項應該是相同的。執行如下**。
c043 c043;
c071 c071;
print_size_detail(c071)
print_vtable_item(c043, 0, 0)
print_vtable_item(c071, 0, 0)
print_vtable_item(c043, 0, 1)
print_vtable_item(c071, 0, 1)
結果為:
the size of c071 is 4
the detail of c071 is 5c b4 45 00
c043 : objadr:0012fcd4 vpadr:0012fcd4 vtadr:0045b450 vtival(0):0041d4f1
c071 : objadr:0012fcc8 vpadr:0012fcc8 vtadr:0045b45c vtival(0):0041d811
c043 : objadr:0012fcd4 vpadr:0012fcd4 vtadr:0045b450 vtival(1):0041dfe1
c071 : objadr:0012fcc8 vpadr:0012fcc8 vtadr:0045b45c vtival(1):0041dfe1
觀察第1、2行的最後一列,即兩個類的虛表的第乙個條目,由於c071重寫了foo1函式,所以這個值不一樣。而第3、4行的最後一列為兩個類的虛表的第二個條目,由於c071並沒有重寫它,所以這兩個值是相同的。和我們之間的猜測是一致的。
(未完待續)
潘凱 C 物件布局及多型實現的探索 五
普通成員函式的呼叫 從這部分開始我們除了利用記憶體的資訊列印來進行探索外,更多的會通過跟蹤和觀察編譯器產生的彙編 來理解編譯器對這些語言特性的實現方式。彙編方面知識的討論超出了本文的範圍,我只對和我們討論相關的彙編 進行解析。理解本文要討論的知識並不需要有很完整的彙編知識,但必須了解起碼的概念。下面...
潘凱 C 物件布局及多型實現的探索 十三
中所定義類的簡單說明 c000 空類。c010 帶普通成員函式及 或變數的頂層類。c011 帶普通成員函式及 或變數的頂層類。c012 帶靜態成員函式及變數的頂層類。c013 從c010繼承的有成員變數及覆蓋父類普通成員函式的類。c014 從c011繼承的空類。c015 從c010,c011繼承的空...
C 物件布局及多型實現的探索
虛函式的類的物件布局 1 如果類中存在虛函式時,情況會怎樣呢?我們知道當乙個類中有虛函式時,編譯器會為該類產生乙個虛函式表,並在它的每乙個物件中插入乙個指向該虛函式表的指標,通常這個指標是插在物件的起始位置。所謂的虛函式表實際就是乙個指標陣列,其中的指標指向真正的函式起始位址。我們來驗證一下,定義乙...