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

字號(hào):

我們?cè)倏纯刺摮蓡T函數(shù)的調(diào)用。類(lèi)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();
    結(jié)果如下:
    The detail of C041 is 14 b3 45 00 01
    obj : objadr:0012F824 vpadr:0012F824 vtadr:0045B314 vtival(0):0041DF1E
    我們打印出了C041的對(duì)象內(nèi)存布局及它的虛表信息。
    先看看obj.foo();的匯編代碼:
    004230DF lea ecx,[ebp FFFFF948h]
    004230E5 call 0041DF1E
    和前一篇文章中看過(guò)的普通的成員函數(shù)調(diào)用產(chǎn)生的匯編代碼一樣。這說(shuō)明了通過(guò)對(duì)象進(jìn)行函數(shù)調(diào)用,即使被調(diào)用的函數(shù)是虛函數(shù)也是靜態(tài)綁定,即在編譯時(shí)決議出函數(shù)的地址。不會(huì)有多態(tài)的行為發(fā)生。
    我們跟蹤進(jìn)去看看函數(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行給類(lèi)的第一個(gè)成員變量賦值,這時(shí)我們可以看到在取變量的地址時(shí)用的是[eax 4],即跳過(guò)了對(duì)象布局最前面的4字節(jié)的虛表指針。
    接下來(lái)我們看看通過(guò)指針進(jìn)行的虛函數(shù)調(diào)用pt->foo();,產(chǎn)生的匯編代碼如下:
    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中就保存了對(duì)象的內(nèi)存地址,同時(shí)也是類(lèi)的虛表指針的地址。第2行取eax中指針指向的值(注意不是 eax的值)到edx寄存器中,實(shí)際上也就是虛表的地址。執(zhí)行完這兩條指令后,我們看看eax和edx中的值,果然和我們前面打印的obj的虛表信息中的 vpadr和vtadr的值是一樣的,分別為0x0012F824和0x0045B314。第4行同樣用ecx寄存器來(lái)保存并傳遞對(duì)象地址,即 this指針的值。第5行的call指令,我們可以看到目的地址不象通過(guò)對(duì)象來(lái)調(diào)用那樣,是一個(gè)直接的函數(shù)地址。而是將edx中的值做為指針來(lái)進(jìn)行間接調(diào)用。前面我們已經(jīng)知道edx中存放的實(shí)際是虛表的地址,我們也知道虛表實(shí)際是一個(gè)指針數(shù)組。這樣第5行的調(diào)用實(shí)際就是取到虛表中的第一個(gè)條目的值,即 C041::foo()函數(shù)的地址。如果被調(diào)用的虛函數(shù)對(duì)應(yīng)的虛表?xiàng)l目的索引不是0,將會(huì)看到edx后加上一個(gè)索引號(hào)乘4后的偏移值。繼承跟蹤可以發(fā)現(xiàn), ptr[edx]的值為0x0041DF1E,也和我們打印的vtival(0)的值相同。前面已經(jīng)提到過(guò),這個(gè)地址實(shí)際也不是真正的函數(shù)地址,是一個(gè)跳轉(zhuǎn)指令,繼續(xù)執(zhí)行就到了真正的函數(shù)代碼部分(即前面列出的代碼)。