在VC6.0中虛函數的實現原理

來源:互聯網
上載者:User

討論虛擬函數的技術內幕——後期聯編(Late binding),

   一,進入記憶體
首先,一個含有虛擬函數的類在記憶體中的結構。
假設一個這樣的類:

class CShape
{
        int b1;
public:
        void MyTest()
        {
                 cout << "CShape::MyTest /n";
        }
};
在棧區,它僅僅只是佔據了四個位元組,用於存放成員資料——b1。 sizeof(CShape)
奇怪,那麼它的成員函數在那裡呢?在VC++6.0,對於普通的成員函數,它將CShape::MyTest()編修改為:“?MyTest@CTestA@@QAEXXZ”,真是個奇怪的名字,但是在這個名字中卻儲存了重要的資訊,比如所屬類,參數類型等。
另外的一個類:
class CShape_V
{
        int b1;
public:
        virtual void play()
        {
                cout << "CShape::play /n";
        }  
        virtual void display()
        {
                cout <<b1<< "Shape /n";
        }
};

在棧區,它佔據了八個位元組,用於存放成員資料b1和指向一個一維數組首地址的指標。

二,vtable

為了達到後期聯編的目的,VC編譯器通過一個表,在執行期間接地調用了實際上需要調用的函數(注意是“間接”),這個表可稱為“虛擬函數地址表”(在很多影印版的圖書中常稱之為vtable),每個類中含有虛擬函數的對象,編譯器都會為它們指定一個虛擬函數地址表,虛擬函數地址表是個函數指標數組,儲存在資料區,它由此類對象所共用(靜態)。此外,編譯器當然也會為它加上一個成員變數,一個指向自己的“虛擬函數地址表”的指標(常稱之為vptr),並且放在了對象的首地址上。
每一個由此類分配出的對象,都有這麼個vptr,每當我們通過這個對象調用虛擬函數時,實際上是通過vptr找到vtable,再通過位移量找出真正的函數地址。
奧妙在於這個vtable以及這種間接調用方式,vtable是按照類中虛擬函式宣告的順序,一一填入函數地址。衍生類別會繼承基類的vtable(當然還有其他可以繼承的成員),當我們在衍生類別裡修改了虛擬函數時,衍生類別的vtable中的內容也被修改,表中相應的元素不在是基類的函數地址,而是衍生類別的函數地址

三,分析:
//vTest.cpp
#include <iostream.h>
//--------------------------------------------
class CShape
{
        int b1;
public:
      
        void MyTest(){
             cout << "CShape::MyTest /n";
        }
        virtual void play()
        {
         cout << "CShape::play /n";
         }
        virtual void display()
        {
        cout <<b1<< "Shape /n";
        }

};
//--------------------------------------------
class       int b2;
public:
      
        void MyTest()
        {
         cout << "CRect::MyTest /n";
        }
         void display()
         {
         cout <<b2<< "Rectangle /n";
         }
};
//--------------------------------------------
class       int b3;
public:   
      
        void MyTest()
        {
        cout << "CSquare::MyTest /n";
        }
        void display()
        {
        cout <<b3<< "Square /n";
        }
};
//--------------------------------------------
void main()
{
        CShape    aShape;
        CRect     aRect;
        CSquare   aSquare;
        CShape* pShape[3] = { &aShape,&aRect,&aSquare };
        for (int i=0; i< 3; i++)
        {
               pShape[i]->display();
               pShape[i]->MyTest();
        }
}

=====================第二部分====================

107:   void main()
108:   {
004117D0    push         ebp
004117D1    mov          ebp,esp
004117D3    sub          esp,74h
004117D6    push         ebx
004117D7    push         esi
004117D8    push         edi
004117D9    lea          edi,[ebp-74h]
004117DC    mov          ecx,1Dh
004117E1    mov          eax,0CCCCCCCCh
004117E6    rep stos     dword ptr [edi]
109:          CShape    aShape;       //aShape 地址0x0012ff78
004117E8    lea          ecx,[ebp-8] ;ebp 為0x0012ff80,執行後ecx為0012ff78,
004117EB    call         @ILT+65(ostream::operator<<) (00401046)
110:          CRect1     aRect;       //aRect 0x0012ff6c
004117F0    lea          ecx,[ebp-14h]   ;ebp 為0x0012ff80,執行後eex為0012ff6c,即指向aRect,也

就是aRect的this指標,相關教材上說this一般儲存在ecx。
004117F3    call         @ILT+80(CRect::CRect) (00401055)   ;建構函式(後面詳細講解)
111:          CSquare   aSquare; //aSquare 地址0x0012ff5c
004117F8    lea          ecx,[ebp-24h] //ecx為0x0012ff5c,ebp為0012ff80
004117FB    call         @ILT+60(CSquare::CSquare) (00401041) ;建構函式
112:          CShape* pShape[3] = { &aShape,&aRect,&aSquare }; //pShape為0x0012ff50
00411800    lea          eax,[ebp-8] //ebp=0x0012ff80;eax=0x0012ff78,即為aShape的地址
00411803    mov          dword ptr [ebp-30h],eax;把eax發到ebp-30h的內容空間裡(4個位元組),

ebp=0x0012ff80,[ebp-30]=0x0012ff50即pShape的地址。
00411806    lea          ecx,[ebp-14h];ecx=0x0012ff6c即為aRect
00411809    mov          dword ptr [ebp-2Ch],ecx //[ebp-2c]=0x0012ff54,即為pShape[1]
0041180C    lea          edx,[ebp-24h]   //同理
0041180F    mov          dword ptr [ebp-28h],edx //同上
113:          for (int i=0; i< 3; i++)   //i地址0x0012ff4c
00411812    mov          dword ptr [ebp-34h],0   //記憶體為0x0012ff80-34h=012ff4c,即i
00411819    jmp          main+54h (00411824)    ;跳到00411824
0041181B    mov          eax,dword ptr [ebp-34h] ;把i給eax;
0041181E    add          eax,1                    ;eax加1
00411821    mov          dword ptr [ebp-34h],eax ;eax再加1,上面三條指令完成i++
00411824    cmp          dword ptr [ebp-34h],3    ;把i和3做比較
00411828    jge          main+84h (00411854)      ;如果不小於則跳到00411854
114:          {
115:                 pShape[i]->display();       ;   //下面以i=1為例,aRect1
0041182A    mov          ecx,dword ptr [ebp-34h] ;ebp-34h為i的記憶體,把i給ecx;
0041182D    mov          ecx,dword ptr [ebp+ecx*4-30h] ;基址+變址定址 ebp-30h為pShape的地址

,ecx(i)為變址,通過i索引數組裡不同的對象;最後ecx得到了當前對象的this指標即對象的地址
00411831    mov          edx,dword ptr [ebp-34h] ;i給edx
00411834    mov          eax,dword ptr [ebp+edx*4-30h];eax同樣得到了對象的地址,eax=0x0012ff6c
00411838    mov          edx,dword ptr [eax] ;[eax]放著對像,對象的第一個資料是vtable的地址,

所以[edx]裡面是對象的第一個虛函數;edx=0x00429114,[edx]=0x00401050
0041183A    mov          esi,esp ;
0041183C    call         dword ptr [edx+4] ;[edx+4]為第二個虛函數的地址。[edx+4]=0x0040105a,虛函數的地址。間接。這個虛函數的調用:取得this指標,得vtable,根據vtable裡面的地址,得出虛函數。
0041183F    cmp          esi,esp               ;//??
00411841    call         __chkesp (004011e0)   ;堆棧清除
116:                 pShape[i]->MyTest();
00411846    mov          eax,dword ptr [ebp-34h] ;i
00411849    mov          ecx,dword ptr [ebp+eax*4-30h] ;對象地址
0041184D    call         @ILT+25(CShape::MyTest) (0040101e) ;//調用CShape::MyTest函數,注意這2個函數的調用方法的區別
117:          }
00411852    jmp          main+4Bh (0041181b)   ;跳回
118:
119:   }
00411854    pop          edi
00411855    pop          esi
00411856    pop          ebx
00411857    add          esp,74h
0041185A    cmp          ebp,esp
0041185C    call         __chkesp (004011e0)
00411861    mov          esp,ebp
00411863    pop          ebp
00411864    ret

分析CRect1 aRect的建構函式:
110:          CRect1     aRect;
004117F0    lea          ecx,[ebp-14h]
004117F3    call         @ILT+80(CRect::CRect) (00401055)
函數從call 進入:跳轉到0x00401055可以看到:
00401055    jmp          CRect1::CRect1 (00411530)
0040105A    jmp          CRect1::display (00411580)
0040105F    int          3
00401060    int          3
00401061    int          3
在跟進去:
73:           CRect1():b2(2){};
00411530    push         ebp
00411531    mov          ebp,esp
00411533    sub          esp,44h
00411536    push         ebx
00411537    push         esi
00411538    push         edi
00411539    push         ecx //堆棧保護
0041153A    lea          edi,[ebp-44h]   //ebp=0x0012fef8 ,edi=0x0012ffb4
0041153D    mov          ecx,11h         ;
00411542    mov          eax,0CCCCCCCCh
00411547    rep stos     dword ptr [edi] ;以上三條把
00411549    pop          ecx               ;0x0012ff6c(好熟悉啊,就是aRect1的地址,this指標)
0041154A    mov          dword ptr [ebp-4],ecx ;
0041154D    mov          ecx,dword ptr [ebp-4]
00411550    call         @ILT+65(ostream::operator<<) (00401046) ;00401046為00401046    jmp          CShape::CShape (004010a0)一看就知道是CShape的建構函式。
00411555    mov          eax,dword ptr [ebp-4] ;把ecx給eax,即this,首地址為vtable的地址
00411558    mov          dword ptr [eax+8],2   ;[eax+4]為b1,[eax+8]為b2,b2=0
0041155F    mov          ecx,dword ptr [ebp-4] ;ecx=this
00411562    mov         dword ptr [ecx],offset CRect1::`vftable' (00429114);把vftable的地址付給[ecx]這個記憶體,即[ecx]裡面是aftable的地址,[ecx]=0x00429114
跟蹤:00429114裡面有2個元素,一個0x00401050 ostream::operator,另外一個0x0040105a :CRect1::display(),初始化的時候,把虛函數的地址放進去。上面調用。
00411568    mov          eax,dword ptr [ebp-4]
0041156B    pop          edi
0041156C    pop          esi
0041156D    pop          ebx
0041156E    add          esp,44h
00411571    cmp          ebp,esp
00411573    call         __chkesp (004011e0)
00411578    mov          esp,ebp
0041157A    pop          ebp
0041157B    ret

    主要部分在藍色部分, 2個地方,前一個是如何調用虛函數,後面一個在建構函式中如何初始化建構函式。

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.