国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發(fā)設計 > 正文

C++對象布局及多態(tài)實現(xiàn)探索之虛函數(shù)調用

2019-11-17 05:08:53
字體:
來源:轉載
供稿:網(wǎng)友

  我們再看看虛成員函數(shù)的調用。類C041中含有虛成員函數(shù),它的定義如下:

strUCt C041
{
C041() : c_(0x01) {}
virtual void foo() { c_ = 0x02; }
char c_;
};
  執(zhí)行如下代碼:

C041 obj;
PRINT_DETAIL(C041, obj)
PRINT_VTABLE_ITEM(obj, 0, 0)
obj.foo();
C041 * pt = &obj;
pt->foo();
  結果如下:

The detail of C041 is 14 b3 45 00 01
obj : objadr:0012F824 vpadr:0012F824 vtadr:0045B314 vtival(0):0041DF1E
  我們打印出了C041的對象內存布局及它的虛表信息。

  先看看obj.foo();的匯編代碼:

004230DF lea ecx,[ebp+FFFFF948h]
004230E5 call 0041DF1E
  和前一篇文章中看過的普通的成員函數(shù)調用產生的匯編代碼一樣。這說明了通過對象進行函數(shù)調用,即使被調用的函數(shù)是虛函數(shù)也是靜態(tài)綁定,即在編譯時決議出函數(shù)的地址。不會有多態(tài)的行為發(fā)生。

  我們跟蹤進去看看函數(shù)的匯編代碼。

01 004263F0 push ebp
02 004263F1 mov ebp,esp
03 004263F3 sub esp,0CCh
04 004263F9 push ebx
05 004263FA push esi
06 004263FB push edi
07 004263FC push ecx
08 004263FD lea edi,[ebp+FFFFFF34h]
09 00426403 mov ecx,33h
10 00426408 mov eax,0CCCCCCCCh
11 0042640D rep stos dWord ptr [edi]
12 0042640F pop ecx
13 00426410 mov dword ptr [ebp-8],ecx
14 00426413 mov eax,dword ptr [ebp-8]
15 00426416 mov byte ptr [eax+4],2
16 0042641A pop edi
17 0042641B pop esi
18 0042641C pop ebx
19 0042641D mov esp,ebp
20 0042641F pop ebp
21 00426420 ret
  值得注重的是第14、15行。第14行把this指針的值移到eax寄存器中,第15行給類的第一個成員變量賦值,這時我們可以看到在取變量的地址時用的是[eax+4],即跳過了對象布局最前面的4字節(jié)的虛表指針。

  接下來我們看看通過指針進行的虛函數(shù)調用pt->foo();,產生的匯編代碼如下:

01 004230F6 mov eax,dword ptr [ebp+FFFFF900h]
02 004230FC mov edx,dword ptr [eax]
03 004230FE mov esi,esp
04 00423100 mov ecx,dword ptr [ebp+FFFFF900h]
05 00423106 call dword ptr [edx]
  第1行把pt指向的地址移入eax寄存器,這樣eax中就保存了對象的內存地址,同時也是類的虛表指針的地址。第2行取eax中指針指向的值(注重不是 eax的值)到edx寄存器中,實際上也就是虛表的地址。執(zhí)行完這兩條指令后,我們看看eax和edx中的值,果然和我們前面打印的obj的虛表信息中的 vpadr和vtadr的值是一樣的,分別為0x0012F824和0x0045B314。第4行同樣用ecx寄存器來保存并傳遞對象地址,即 this指針的值。第5行的call指令,我們可以看到目的地址不象通過對象來調用那樣,是一個直接的函數(shù)地址。而是將edx中的值做為指針來進行間接調用。前面我們已經知道edx中存放的實際是虛表的地址,我們也知道虛表實際是一個指針數(shù)組。這樣第5行的調用實際就是取到虛表中的第一個條目的值,即 C041::foo()函數(shù)的地址。假如被調用的虛函數(shù)對應的虛表條目的索引不是0,將會看到edx后加上一個索引號乘4后的偏移值。繼續(xù)跟蹤可以發(fā)現(xiàn), ptr[edx]的值為0x0041DF1E,也和我們打印的vtival(0)的值相同。前面已經提到過,這個地址實際也不是真正的函數(shù)地址,是一個跳轉指令,繼續(xù)執(zhí)行就到了真正的函數(shù)代碼部分(即前面列出的代碼)。

  我們在上面看到的這個過程,就是動態(tài)綁定的過程。因為我們是通過指針來調用虛成員函數(shù),所以會產生動態(tài)綁定,即使指針的類型和對象的類型是一樣的。為了保證多態(tài)的語義,編譯器在產生call指令時,不象靜態(tài)綁定時那樣,是在編譯時決議出一個確定的地址值。相反它是通過用發(fā)出調用的指針指向的對象中的虛指針,來迂回的找到對象所對應類型的虛表,及虛表中相應條目中存放的函數(shù)地址。這樣具體調用哪個函數(shù)就與指針的類型是無關的,只與具體的對象相關,因為虛指針是存放在具體的對象中,而虛表只和對象的類型相關。這也就是多態(tài)會發(fā)生的原因。

  請回憶一下前面討論過的C071類,當子類重寫從父類繼續(xù)的虛函數(shù)時,子類的虛表內容的變化,及和父類虛表內容的區(qū)別(請參照第二篇中打印的子類和父類的虛表信息)。具體的通過指向子類對象的父類指針來調用被子類重寫過的虛函數(shù)時的調用過程,請有愛好的朋友自己調試一下,這里不再列出。

  另外前面在《C++對象布局及多態(tài)實現(xiàn)之動態(tài)和強制轉換》中我們討論了指針的類型動態(tài)轉換。我們在這里再利用C041、C042及C051類,來看看指針的類型動態(tài)轉換。這幾個類的定義請參見前文。類C051從C041和C042多重繼續(xù)而來,且后兩個類都有虛函數(shù)。執(zhí)行如下代碼:


C051 obj;
C041 * pt1 = dynamic_cast<C041*>(&obj);
C042 * pt2 = dynamic_cast<C042*>(&obj);
pt1->foo();
pt2->foo2();
  第一個動態(tài)轉型對應的匯編代碼為:

00404B59 lea eax,[ebp+FFFFF8ECh]
00404B5F mov dword ptr [ebp+FFFFF8E0h],eax
  因為不需要調整指針位置,所以很直接,取出對象的地址后直接賦給了指針。

  第二個動態(tài)轉型牽涉到了指針位置的調整,我們來看看它的匯編代碼:

01 00404B65 lea eax,[ebp+FFFFF8ECh]
02 00404B6B test eax,eax
03 00404B6D je 00404B7D
04 00404B6F lea ecx,[ebp+FFFFF8F1h]
05 00404B75 mov dword ptr [ebp+FFFFF04Ch],ecx
06 00404B7B jmp 00404B87
07 00404B7D mov dword ptr [ebp+FFFFF04Ch],0
08 00404B87 mov edx,dword ptr [ebp+FFFFF04Ch]
09 00404B8D mov dword ptr [ebp+FFFFF8D4h],edx
  代碼要復雜的多。&obj運算后得到的是一個指針,前三行指令就是判定這個指針是否為NULL。希奇的是第4行并沒有根據(jù)eax中的地址(即對象的起始地址)來進行指針的位置調整,而是直接把[ebp+FFFFF8F1h]的地址取到ecx寄存器中。第1行指令中的[ebp+ FFFFF8ECh]實際是得到對象的地址,ebp所加的那個數(shù)實際是個負數(shù)(補碼)也就是對象的偏移地址。對比兩個數(shù)發(fā)現(xiàn)相差5字節(jié),這樣實際上第4行是直接得到了指針調整后的地址,即將指針指向了對象中的屬于C042的部分。后面的代碼又通過一個臨時變量及edx寄存器把調整后的指針值最終存到了 pt2指針中。

  這些代碼實際可以優(yōu)化成二行:

lea eax, [ebp+FFFFF8F1h]
mov dword ptr [ebp+FFFFF8d4h], eax
  我們曾提到C051類有兩個虛表,相應對象中有也兩個虛表指針,之所以不合并為一個,就是為了處理指針的類型動態(tài)轉換。結合前面對于多態(tài)的討論,我們就可以理解得更清楚了。pt2->foo2();調用時,對象的類型還是C051,但經過指針動態(tài)轉換pt2指向了對象中屬于C042的部分的起始,也就是第二個虛表指針。這樣在進行函數(shù)調用時就不需要再做額外的處理了。我們看看pt1->foo();及pt2->foo2 ();產生的匯編碼即知。

01 00404B93 mov eax,dword ptr [ebp+FFFFF8E0h]
02 00404B99 mov edx,dword ptr [eax]
03 00404B9B mov esi,esp
04 00404B9D mov ecx,dword ptr [ebp+FFFFF8E0h]
05 00404BA3 call dword ptr [edx]
06 00404BA5 cmp esi,esp
07 00404BA7 call 0041DDDE
08 00404BAC mov eax,dword ptr [ebp+FFFFF8D4h]
09 00404BB2 mov edx,dword ptr [eax]
10 00404BB4 mov esi,esp
11 00404BB6 mov ecx,dword ptr [ebp+FFFFF8D4h]
12 00404BBC call dword ptr [edx]
13 00404BBE cmp esi,esp
14 00404BC0 call 0041DDDE
  前7行為pt1->foo();,后7行為pt2->foo2();。唯一不同的是指針指向的地址不同,調用機制是一樣的。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 湖州市| 眉山市| 平舆县| 句容市| 灵武市| 攀枝花市| 桓台县| 虹口区| 正镶白旗| 许昌市| 宣城市| 左云县| 曲麻莱县| 绵阳市| 巴林右旗| 大竹县| 沁源县| 来凤县| 龙胜| 集安市| 荆门市| 北碚区| 普兰县| 武安市| 巴马| 乐至县| 阜南县| 北京市| 永平县| 鹰潭市| 伊宁县| 正安县| 昆山市| 青海省| 宁河县| 无为县| 大化| 安龙县| 睢宁县| 霍城县| 崇信县|