潘凱 C 物件布局及多型實現的探索 二

2021-04-13 08:55:20 字數 2760 閱讀 9018

帶虛函式的類的物件布局(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 如果類中存在虛函式時,情況會怎樣呢?我們知道當乙個類中有虛函式時,編譯器會為該類產生乙個虛函式表,並在它的每乙個物件中插入乙個指向該虛函式表的指標,通常這個指標是插在物件的起始位置。所謂的虛函式表實際就是乙個指標陣列,其中的指標指向真正的函式起始位址。我們來驗證一下,定義乙...