程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 用匯編的眼光看C++(之虛函數)

用匯編的眼光看C++(之虛函數)

編輯:C++入門知識

 

【 聲明:版權所有,歡迎轉載,請勿用於商業用途。  聯系信箱:feixiaoxing @163.com】 

 

 

 

 

    虛函數是面向對象設計中的一個重要內容。它的出現使得我們只需要相同的接口函數,並可以得到不同的生成結果。但是有些朋友卻知其然,不知其所以然,為什麼會出現這樣的結果,我們可以用一段代碼說明問題。首先,我們先定義兩個基本類型,一個是employee,一個是manager,看過前面一片博客的朋友應該都有點印象:

 

 

class employee 

public: 

    employee() { } 

    ~employee() {} 

    virtual void print() const { printf("employee!\n");} 

}; 

 

class manager : public employee 

public: 

    manager() {} 

    ~manager() {} 

    void print() const {printf("manager!\n");} 

}; 

class employee

{

public:

       employee() { }

       ~employee() {}

       virtual void print() const { printf("employee!\n");}

};

 

class manager : public employee

{

public:

       manager() {}

       ~manager() {}

       void print() const {printf("manager!\n");}

};

    我們看到,和前面出現的成員函數稍微有一些不同,這裡的print函數之前出現了virtual。然而正是這個virtual發揮了巨大的作用。可以毫不誇張地說,沒有虛函數,基本上就沒有設計模式,也就無法體現C++語言在面向對象設計中的巨大優越性。下面我們看看這個virtual是怎樣發揮作用的?

 

 

76:       employee p; 

0040128D   lea         ecx,[ebp-10h] 

00401290   call        @ILT+45(employee::employee) (00401032) 

00401295   mov         dword ptr [ebp-4],0 

77:       manager m; 

0040129C   lea         ecx,[ebp-14h] 

0040129F   call        @ILT+65(manager::manager) (00401046) 

004012A4   mov         byte ptr [ebp-4],1 

78:       employee* e = &p; 

004012A8   lea         eax,[ebp-10h] 

004012AB   mov         dword ptr [ebp-18h],eax 

79:       e->print(); 

004012AE   mov         ecx,dword ptr [ebp-18h] 

004012B1   mov         edx,dword ptr [ecx] 

004012B3   mov         esi,esp 

004012B5   mov         ecx,dword ptr [ebp-18h] 

004012B8   call        dword ptr [edx] 

004012BA   cmp         esi,esp 

004012BC   call        __chkesp (00408870) 

80:       e = &m; 

004012C1   lea         eax,[ebp-14h] 

004012C4   mov         dword ptr [ebp-18h],eax 

81:       e->print(); 

004012C7   mov         ecx,dword ptr [ebp-18h] 

004012CA   mov         edx,dword ptr [ecx] 

004012CC   mov         esi,esp 

004012CE   mov         ecx,dword ptr [ebp-18h] 

004012D1   call        dword ptr [edx] 

004012D3   cmp         esi,esp 

004012D5   call        __chkesp (00408870) 

82:   } 

76:       employee p;

0040128D   lea         ecx,[ebp-10h]

00401290   call        @ILT+45(employee::employee) (00401032)

00401295   mov         dword ptr [ebp-4],0

77:       manager m;

0040129C   lea         ecx,[ebp-14h]

0040129F   call        @ILT+65(manager::manager) (00401046)

004012A4   mov         byte ptr [ebp-4],1

78:       employee* e = &p;

004012A8   lea         eax,[ebp-10h]

004012AB   mov         dword ptr [ebp-18h],eax

79:       e->print();

004012AE   mov         ecx,dword ptr [ebp-18h]

004012B1   mov         edx,dword ptr [ecx]

004012B3   mov         esi,esp

004012B5   mov         ecx,dword ptr [ebp-18h]

004012B8   call        dword ptr [edx]

004012BA   cmp         esi,esp

004012BC   call        __chkesp (00408870)

80:       e = &m;

004012C1   lea         eax,[ebp-14h]

004012C4   mov         dword ptr [ebp-18h],eax

81:       e->print();

004012C7   mov         ecx,dword ptr [ebp-18h]

004012CA   mov         edx,dword ptr [ecx]

004012CC   mov         esi,esp

004012CE   mov         ecx,dword ptr [ebp-18h]

004012D1   call        dword ptr [edx]

004012D3   cmp         esi,esp

004012D5   call        __chkesp (00408870)

82:   }

    上面是一段函數調用的代碼,代碼可以稍微有點長。不過沒有關系,我們可以按照代碼的行數一行一行地去進行說明和理解。

 

    76行: 我們創建了employee類型的一個變量p,這個可以從後面的employee的構造函數可以看出來

 

    77行: 我們創建了manager類型的一個變量,這個也可以從後面的manager的構造函數看出

 

    78行: 我們創建一個指針臨時變量e,它保存了變量p的地址,這一句也比較簡單

 

    79行: 我們發現79句下面共有7句匯編,其中第三句、第六句、第七句是平衡堆棧的時候用的,和我們的調用沒有關系。那麼call的edx是什麼東西呢?原來函數調用的順序是這樣的:edx -> [ecx]  ->[ebp-0x18],不知道大家看明白了沒有。在內存的第一個字節記錄一個指向print函數指針的指針,也就是edx。通過這個edx,我們就可以查找到位於edx地址的內容是什麼。後來我們提取出來後發現[edx]的內容正是我們要查找的print函數地址。這裡相當於一個二次尋址的過程。

 

    80行: 我們重新對臨時變量e進行了賦值,此時e保存的是變量m的地址

 

    81行: 我們發現此時的尋找過程和79行驚奇地一致,原因就在於edx的內容不同罷了。也就是指向函數指針的指針發生了變化而已。

 

 

 

 

    試想一下,如果沒有這個virtual函數,以上這段代碼會發生什麼差別呢?

 

 

76:       employee p; 

0040127D   lea         ecx,[ebp-10h] 

00401280   call        @ILT+45(employee::employee) (00401032) 

00401285   mov         dword ptr [ebp-4],0 

77:       manager m; 

0040128C   lea         ecx,[ebp-14h] 

0040128F   call        @ILT+65(manager::manager) (00401046) 

00401294   mov         byte ptr [ebp-4],1 

78:       employee* e = &p; 

00401298   lea         eax,[ebp-10h] 

0040129B   mov         dword ptr [ebp-18h],eax 

79:       e->print(); 

0040129E   mov         ecx,dword ptr [ebp-18h] 

004012A1   call        @ILT+5(employee::print) (0040100a) 

80:       e = &m; 

004012A6   lea         ecx,[ebp-14h] 

004012A9   mov         dword ptr [ebp-18h],ecx 

81:       e->print(); 

004012AC   mov         ecx,dword ptr [ebp-18h] 

004012AF   call        @ILT+5(employee::print) (0040100a) 

82:   } 

76:       employee p;

0040127D   lea         ecx,[ebp-10h]

00401280   call        @ILT+45(employee::employee) (00401032)

00401285   mov         dword ptr [ebp-4],0

77:       manager m;

0040128C   lea         ecx,[ebp-14h]

0040128F   call        @ILT+65(manager::manager) (00401046)

00401294   mov         byte ptr [ebp-4],1

78:       employee* e = &p;

00401298   lea         eax,[ebp-10h]

0040129B   mov         dword ptr [ebp-18h],eax

79:       e->print();

0040129E   mov         ecx,dword ptr [ebp-18h]

004012A1   call        @ILT+5(employee::print) (0040100a)

80:       e = &m;

004012A6   lea         ecx,[ebp-14h]

004012A9   mov         dword ptr [ebp-18h],ecx

81:       e->print();

004012AC   mov         ecx,dword ptr [ebp-18h]

004012AF   call        @ILT+5(employee::print) (0040100a)

82:   }

    很遺憾,這裡就沒有了動態查找的過程,所有的打印函數最終都指向了函數employee::print,此時多態性也不復存在了。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved