通過彙編角度理解虛函數!

來源:互聯網
上載者:User

今天通過彙編角度在次理解一下虛函數.

工作環境VC6

方法DEBUG 跟蹤MEMORY

測試程式碼 雖然是測試程式但希望大家還是養成良好習慣 不要汙染命名空間

測試代碼:

#include "iostream"
using std::cout;
using std::endl;

class a
{
  int m_a;
public:
  a(int x):m_a(x) {}

  virtual void display()
  {
    cout<<"a is running !"<<endl;
  }
};

class b : public a
{
public:
  b(int y):a(y) {}

  void display()
  {
    cout<<"b is running !"<<endl;
  }
};

class c : public b
{
public:
  c(int z):b(z) {}

  void display()
  {
    cout<<"c is running !"<<endl;
  }
};

int main()
{
  a ca(1);
  b cb(2);
  c cc(3);

  a * p[3]={ &ca , &cb , &cc };

  p[0]->display();
  p[1]->display();
  p[2]->display();

  return 0;
}

輸出結果就不用多說了,如果你還不能想到正確結果 那先看一下基礎知識在看這篇文章.

先看一下產生的彙編代碼

 39:   int main()
40:   {
004011A0   push        ebp
004011A1   mov         ebp,esp
004011A3   sub         esp,64h
004011A6   push        ebx
004011A7   push        esi
004011A8   push        edi
004011A9   lea         edi,[ebp-64h]
004011AC   mov         ecx,19h
004011B1   mov         eax,0CCCCCCCCh
004011B6   rep stos    dword ptr [edi]
41:           a ca(1);
004011B8   push        1
004011BA   lea         ecx,[ebp-8]
004011BD   call        @ILT+95(a::a) (00401064)
42:           b cb(2);
004011C2   push        2
004011C4   lea         ecx,[ebp-10h]
004011C7   call        @ILT+10(b::b) (0040100f)
43:           c cc(3);
004011CC   push        3
004011CE   lea         ecx,[ebp-18h]
004011D1   call        @ILT+105(c::c) (0040106e)
44:
45:           a * p[3]={ &ca , &cb , &cc };
004011D6   lea         eax,[ebp-8]
004011D9   mov         dword ptr [ebp-24h],eax
004011DC   lea         ecx,[ebp-10h]
004011DF   mov         dword ptr [ebp-20h],ecx
004011E2   lea         edx,[ebp-18h]
004011E5   mov         dword ptr [ebp-1Ch],edx
46:
47:           p[0]->display();
004011E8   mov         eax,dword ptr [ebp-24h]
004011EB   mov         edx,dword ptr [eax]
004011ED   mov         esi,esp
004011EF   mov         ecx,dword ptr [ebp-24h]
004011F2   call        dword ptr [edx]
004011F4   cmp         esi,esp
004011F6   call        __chkesp (004092c0)
48:           p[1]->display();
004011FB   mov         eax,dword ptr [ebp-20h]
004011FE   mov         edx,dword ptr [eax]
00401200   mov         esi,esp
00401202   mov         ecx,dword ptr [ebp-20h]
00401205   call        dword ptr [edx]
00401207   cmp         esi,esp
00401209   call        __chkesp (004092c0)
49:           p[2]->display();
0040120E   mov         eax,dword ptr [ebp-1Ch]
00401211   mov         edx,dword ptr [eax]
00401213   mov         esi,esp
00401215   mov         ecx,dword ptr [ebp-1Ch]
00401218   call        dword ptr [edx]
0040121A   cmp         esi,esp
0040121C   call        __chkesp (004092c0)
50:
51:           return 0;
00401221   xor         eax,eax

我們只需要看一下重要的代碼就可以了

41:           a ca(1);
004011B8   push        1
004011BA   lea         ecx,[ebp-8]
004011BD   call        @ILT+95(a::a) (00401064)

構造A類對象,先壓棧 常量1 作為參數.

將棧中的一個地址給ECX ,此時ECX 要作為該對象的THIS 指標.現在的這塊記憶體是全CC CC CC CC....

調用A類的建構函式.因為不是VIRTUAL FNCTION 所以是CALL一個常量地址,到一個跳錶

00401064   jmp         a::a (00401260)
進入到A類的建構函式.

9:            a(int x):m_a(x) {}
00401260   push        ebp
00401261   mov         ebp,esp
00401263   sub         esp,44h
00401266   push        ebx
00401267   push        esi
00401268   push        edi
00401269   push        ecx
0040126A   lea         edi,[ebp-44h]
0040126D   mov         ecx,11h
00401272   mov         eax,0CCCCCCCCh
00401277   rep stos    dword ptr [edi]
00401279   pop         ecx
0040127A   mov         dword ptr [ebp-4],ecx
0040127D   mov         eax,dword ptr [ebp-4]
00401280   mov         ecx,dword ptr [ebp+8]
00401283   mov         dword ptr [eax+4],ecx
00401286   mov         edx,dword ptr [ebp-4]
00401289   mov         dword ptr [edx],offset a::`vftable' (0043201c)
0040128F   mov         eax,dword ptr [ebp-4]
00401292   pop         edi
00401293   pop         esi
00401294   pop         ebx
00401295   mov         esp,ebp
00401297   pop         ebp
00401298   ret         4

我們只看最重要的幾行就可以了

0040127A   mov         dword ptr [ebp-4],ecx
0040127D   mov         eax,dword ptr [ebp-4]
00401280   mov         ecx,dword ptr [ebp+8]
00401283   mov         dword ptr [eax+4],ecx
00401286   mov         edx,dword ptr [ebp-4]
00401289   mov         dword ptr [edx],offset a::`vftable' (0043201c)
0040128F   mov         eax,dword ptr [ebp-4]

 此時ECX是該對象的THIS 指標.把THIS指標給一個變數(棧來實現) . 在給EAX 所以此時EAX也是THIS指標.

mov         ecx,dword ptr [ebp+8]
實現了將剛才壓入棧中的參數1 給ECX.

mov         dword ptr [eax+4],ecx
該值在給以THISI 指標開始+4到+8的4個位元組賦值(因為是 INT型 所以為4個位元組.)  因為A類有虛函數,我們知道 編譯器會給A類對象的前4個位元組賦予類A的VTABLE的地址,這4個位元組就是我們常說的VPTR 它指向虛表.所以VPTR後的4個位元組才是存放對象的唯一一個資料成員(INT).

此時該對象的記憶體為CC CC CC CC 01 00 00 00  VPTR還沒賦值

mov         edx,dword ptr [ebp-4]
THIS指標給EDX

mov         dword ptr [edx],offset a::`vftable' (0043201c)
把類A虛表地址 給VPTR   也就是THIS指標開始的4個位元組

eax,dword ptr [ebp-4]  還是把THIS指標給EAX 我認為這是一條廢語句

此時該對象的記憶體是

0012FF78  1C 20 43 00 01 00 00 00    虛表指標  資料成員

類B

42:           b cb(2);
004011C2   push        2
004011C4   lea         ecx,[ebp-10h]
004011C7   call        @ILT+10(b::b) (0040100f)

同樣壓棧2 作為參數.  並找到下一個8位元組空間作為類B對象使用的記憶體

並進入跳錶0040100F   jmp         b::b (00401310)

進入建構函式

20:           b(int y):a(y) {}
00401310   push        ebp
00401311   mov         ebp,esp
00401313   sub         esp,44h
00401316   push        ebx
00401317   push        esi
00401318   push        edi
00401319   push        ecx
0040131A   lea         edi,[ebp-44h]
0040131D   mov         ecx,11h
00401322   mov         eax,0CCCCCCCCh
00401327   rep stos    dword ptr [edi]
00401329   pop         ecx
0040132A   mov         dword ptr [ebp-4],ecx
0040132D   mov         eax,dword ptr [ebp+8]
00401330   push        eax
00401331   mov         ecx,dword ptr [ebp-4]
00401334   call        @ILT+95(a::a) (00401064)
00401339   mov         ecx,dword ptr [ebp-4]
0040133C   mov         dword ptr [ecx],offset b::`vftable' (00432034)
00401342   mov         eax,dword ptr [ebp-4]
00401345   pop         edi
00401346   pop         esi
00401347   pop         ebx
00401348   add         esp,44h
0040134B   cmp         ebp,esp
0040134D   call        __chkesp (004092c0)
00401352   mov         esp,ebp
00401354   pop         ebp
00401355   ret         4

主要來看這段

0040132A   mov         dword ptr [ebp-4],ecx
0040132D   mov         eax,dword ptr [ebp+8]
00401330   push        eax
00401331   mov         ecx,dword ptr [ebp-4]
00401334   call        @ILT+95(a::a) (00401064)
00401339   mov         ecx,dword ptr [ebp-4]
0040133C   mov         dword ptr [ecx],offset b::`vftable' (00432034)
00401342   mov         eax,dword ptr [ebp-4]

ECX還是該對象的THIS指標.給一個變數

把參數2給EAX,把EAX壓棧 作為參數來調用基類的建構函式. 函數和上邊說的一樣,所以返回時該對象記憶體為

1C 20 43 00 02 00 00 00 此時虛表指標指向的是類A的VTABLE

ecx,dword ptr [ebp-4]

把THIS指標給ECX

dword ptr [ecx],offset b::`vftable' (00432034)
把B類的VTABLE地址給THIS指標開始的4個位元組 (VPTR)  所以現在對象的記憶體是

34 20 43 00 02 00 00 00

 

類C的和前邊兩個基本相同.最後該對象的記憶體是 4C 20 43 00 03 00 00 00

最後3個對象的記憶體為

4C 20 43 00     03 00 00 00        34 20 43 00        02 00 00 00  燙燙L C.....4 C.....
0012FF78           1C 20 43 00       01 00 00 00

45:           a * p[3]={ &ca , &cb , &cc };
004011D6   lea         eax,[ebp-8]
004011D9   mov         dword ptr [ebp-24h],eax
004011DC   lea         ecx,[ebp-10h]
004011DF   mov         dword ptr [ebp-20h],ecx
004011E2   lea         edx,[ebp-18h]
004011E5   mov         dword ptr [ebp-1Ch],edx

把3個對象的THIS指標賦值給3個4位元組的指標數組.

 

 

47:           p[0]->display();
004011E8   mov         eax,dword ptr [ebp-24h]
004011EB   mov         edx,dword ptr [eax]
004011ED   mov         esi,esp
004011EF   mov         ecx,dword ptr [ebp-24h]
004011F2   call        dword ptr [edx]
004011F4   cmp         esi,esp
004011F6   call        __chkesp (004092c0)
48:           p[1]->display();
004011FB   mov         eax,dword ptr [ebp-20h]
004011FE   mov         edx,dword ptr [eax]
00401200   mov         esi,esp
00401202   mov         ecx,dword ptr [ebp-20h]
00401205   call        dword ptr [edx]
00401207   cmp         esi,esp
00401209   call        __chkesp (004092c0)
49:           p[2]->display();
0040120E   mov         eax,dword ptr [ebp-1Ch]
00401211   mov         edx,dword ptr [eax]
00401213   mov         esi,esp
00401215   mov         ecx,dword ptr [ebp-1Ch]
00401218   call        dword ptr [edx]
0040121A   cmp         esi,esp
0040121C   call        __chkesp (004092c0)
50:
51:           return 0;
00401221   xor         eax,eax

這裡就是虛函數調用,他們因為虛表不同,所以輸出的結果 調用的函數是不同的.

先看看

47:           p[0]->display();
004011E8   mov         eax,dword ptr [ebp-24h]
004011EB   mov         edx,dword ptr [eax]
004011ED   mov         esi,esp
004011EF   mov         ecx,dword ptr [ebp-24h]
004011F2   call        dword ptr [edx]
004011F4   cmp         esi,esp
004011F6   call        __chkesp (004092c0)

把第一個對象的THIS指標給EAX, 

mov         edx,dword ptr [eax]

THIS指標開始的4個位元組取內容.給EDX, 此時EDX就是VPTR,也就是VTABLE的地址

mov         ecx,dword ptr [ebp-24h]

把THIS指標在給ECX

call        dword ptr [edx]

每個類的虛函數在虛表中的位置就靠它在類中聲明的位置而定,沒個虛函數的位置就做為了VTABLE中的ID,因為測試代碼為每個類只定義了一個VIRTUAL FUNCTION 所以這裡就CALL了EDX指向的VTABLE開始的4位元組的那個為一一個VIRTUAL函數地址

跟蹤一下記憶體

0012FF78  1C 20 43 00

跟蹤[eax]      EDX = 0043201C

跟蹤[edx]     0043201C         5F 10 40 00

跟蹤     0040105F   jmp         a::display (004012b0)

於是調用了A類的DISPLAY()函數

後邊的兩個調用一樣會利用VTABLE來調用虛函數.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.