編譯器如何處理C++不同類中同名函數(參數類型個數都相同)


 轉載請注明出處,版權歸作者所有

         lyzaily@126.com

         yanzhong.lee

        作者按:

                  從這篇文章中,我們主要會認識到一下幾點:

                  一、不類中的特征標相同的同名函數,它們是不同的函數,原因就是類具有“名稱空間”的功能;

                  二、類的對象是不包含類聲明中所提到的成員函數所占的內存,對象只包含類聲明中非static成員數據,如類聲明中有虛函數,則對象還會有個vtbl指針。同一個類的所有對象都是使用同一份成員函數拷貝。

                  三、VS編譯器是如何實現函數的重載的,以及如何使用this指針的。

         這是寫給C++初學者的文章,高手就不用浪費您寶貴的時間看了。

        我們知道在同一個類中函數不能完全相同,當然可以重載了,我所要說的是函數名以及它的特征標完全一樣的函數在類中只能有一個,但是在不同的類中可以同時存在函數名以及特征標完全一致的函數,這個是為什么呢?這個就是C++類的功勞,這個有點類似於C++中的“名稱空間”功能。

         那么這些C++語言特性又是如何得到C++編譯支持的呢?

為了研究這個問題,我特地寫了一個測試程序如下:

//CTestA類

class CTestA{
public:
 CTestA();
 ~CTestA();
 int func(int param);
 float func(float param);
private:
 int m_value;
 float m_float;
};
int CTestA::func(int param)
{
 m_value += 1;
 return 0;
}
float CTestA::func(float param)
{
 m_float += 1.0;
 return m_float;
}
CTestA::CTestA()
{
 m_value = 0;
 m_float = 0.0;
}
CTestA::~CTestA()
{
}

 

//CTestB類
class CTestB{
public:
 CTestB();
 ~CTestB();
 int func(int param);
private:
 int m_value;
};

int CTestB::func(int param)
{
 m_value += 1;
 return 0;
}
CTestB::CTestB()
{
 m_value = 0;
}
CTestB::~CTestB()
{
}

//main函數
int _tmain(int argc, _TCHAR* argv[])
{
 float x = 2.0;
 CTestA testA;
 CTestB testB;

 testA.func(1);
 testA.func(x);     //OK
 testA.func(2.0f); //Ok
 //testA.func(2.0); //error ambiguous call

 testB.func(2);
 return 0;
} 

上面是源代碼,我們在VS2005中看到的對應的匯編代碼是這樣的:

--- f:\game\classtest\classtest\classtest.cpp ----------------------------------


int _tmain(int argc, _TCHAR* argv[])
{
00411610  push        ebp  
00411611  mov         ebp,esp 
00411613  push        0FFFFFFFFh 
00411615  push        offset __ehhandler$_wmain (414830h) 
0041161A  mov         eax,dword ptr fs:[00000000h] 
00411620  push        eax  
00411621  sub         esp,0F4h 
00411627  push        ebx  
00411628  push        esi  
00411629  push        edi  
0041162A  lea         edi,[ebp-100h] 
00411630  mov         ecx,3Dh 
00411635  mov         eax,0CCCCCCCCh 
0041163A  rep stos    dword ptr es:[edi] 
0041163C  mov         eax,dword ptr [___security_cookie (418000h)] 
00411641  xor         eax,ebp 
00411643  push        eax  
00411644  lea         eax,[ebp-0Ch] 
00411647  mov         dword ptr fs:[00000000h],eax 
 float x = 2.0;
0041164D  fld         dword ptr [__real@40000000 (416750h)] 
00411653  fstp        dword ptr [ebp-14h] 
 CTestA testA;
00411656  lea         ecx,[ebp-24h] 
00411659  call        CTestA::CTestA (411163h) 
0041165E  mov         dword ptr [ebp-4],0 
 CTestB testB;
00411665  lea         ecx,[ebp-30h] 
00411668  call        CTestB::CTestB (41116Dh) 
0041166D  mov         byte ptr [ebp-4],1

 testA.func(1);
00411671  push        1    
00411673  lea         ecx,[ebp-24h] 
00411676  call        CTestA::func (4110F0h) 
 testA.func(x);
0041167B  push        ecx  
0041167C  fld         dword ptr [ebp-14h] 
0041167F  fstp        dword ptr [esp] 
00411682  lea         ecx,[ebp-24h] 
00411685  call        CTestA::func (4110EBh) 
0041168A  fstp        st(0) 
 testA.func(2.0f);
0041168C  push        ecx  
0041168D  fld         dword ptr [__real@40000000 (416750h)] 
00411693  fstp        dword ptr [esp] 
00411696  lea         ecx,[ebp-24h] 
00411699  call        CTestA::func (4110EBh) 
0041169E  fstp        st(0) 
 //testA.func(2.0); //error ambiguous call

 testB.func(2);
004116A0  push        2    
004116A2  lea         ecx,[ebp-30h] 
004116A5  call        CTestB::func (4110B9h) 
 return 0;
004116AA  mov         dword ptr [ebp-0FCh],0 
004116B4  mov         byte ptr [ebp-4],0 
004116B8  lea         ecx,[ebp-30h] 
004116BB  call        CTestB::~CTestB (4110FFh) 
004116C0  mov         dword ptr [ebp-4],0FFFFFFFFh 
004116C7  lea         ecx,[ebp-24h] 
004116CA  call        CTestA::~CTestA (411104h) 
004116CF  mov         eax,dword ptr [ebp-0FCh] 
}
004116D5  push        edx  
004116D6  mov         ecx,ebp 
004116D8  push        eax  
004116D9  lea         edx,[ (411708h)] 
004116DF  call        @ILT+135(@_RTC_CheckStackVars@8) (41108Ch) 
004116E4  pop         eax  
004116E5  pop         edx  
004116E6  mov         ecx,dword ptr [ebp-0Ch] 
004116E9  mov         dword ptr fs:[0],ecx 
004116F0  pop         ecx  
004116F1  pop         edi  
004116F2  pop         esi  
004116F3  pop         ebx  
004116F4  add         esp,100h 
004116FA  cmp         ebp,esp 
004116FC  call        @ILT+345(__RTC_CheckEsp) (41115Eh) 
00411701  mov         esp,ebp 
00411703  pop         ebp  
00411704  ret              
00411705  lea         ecx,[ecx] 
00411708  db          02h  
00411709  db          00h  
0041170A  db          00h  
0041170B  db          00h  
0041170C  db          10h  
0041170D  db          17h  
0041170E  db          41h  
0041170F  db          00h  
00411710  db          dch  
00411711  db          ffh  
00411712  db          ffh  
00411713  db          ffh  
00411714  db          08h  
00411715  db          00h  
00411716  db          00h  
00411717  db          00h  
00411718  db          2eh  
00411719  db          17h  
0041171A  db          41h  
0041171B  db          00h  
0041171C  db          d0h  
0041171D  db          ffh  
0041171E  db          ffh  
0041171F  db          ffh  
00411720  db          04h  
00411721  db          00h  
00411722  db          00h  
00411723  db          00h  
00411724  db          28h  
00411725  db          17h  
00411726  db          41h  
00411727  db          00h  
00411728  db          74h  
00411729  db          65h  
0041172A  db          73h  
0041172B  db          74h  
0041172C  db          42h  
0041172D  db          00h  
0041172E  db          74h  
0041172F  db          65h  
00411730  db          73h  
00411731  db          74h  
00411732  db          41h  
00411733  db          00h  
--- No source file -------------------------------------------------------------

我們看到匯編代碼中用藍色標出的兩條匯編語句,就是兩條調用兩個類中func函數的跳轉語句。

00411676  call        CTestA::func (4110F0h) 
004116A5  call        CTestB::func (4110B9h) 
其中,00411676和004116A5是這個兩個語句的本身地址,而在每條語句的最后面用括號括起來的,如 (4110F0h) 
和(4110B9h) 都是call指令要去的目的地址,這還不是func函數所在內存的地址,只是一個跳轉指令的地址。我們可以看出這個兩個函數的地址是不一樣的,這個就說明兩個類中具有相同特征標的同名方法,在內存中有不占據不同的內存區域。那么編譯器是如何區別這樣的函數呢,我們從上面藍色標志的兩句匯編語句就可以知道,是使用了類的作用域來表示,如CTestA::和CTestB::,這類似於“名稱空間”功能。

下面的代碼片段,就是在語句“00411676  call        CTestA::func (4110F0h) ”處按F11跳轉到的目的地。如下面紅色標志的一句。

004110F0  jmp         CTestA::func (4113F0h)

這紅色標志的語句jmp CTestA::func(4113F0h)才真正的跳到類CTestA中的func內存處,而該函數的首地址就是4113F0h,如下代碼段(紅色標志的一句):

int CTestA::func(int param)
{
004113F0  push        ebp  
004113F1  mov         ebp,esp 
004113F3  sub         esp,0CCh 
004113F9  push        ebx  
004113FA  push        esi  
004113FB  push        edi  
004113FC  push        ecx  
004113FD  lea         edi,[ebp-0CCh] 
00411403  mov         ecx,33h 
00411408  mov         eax,0CCCCCCCCh 
0041140D  rep stos    dword ptr es:[edi] 
0041140F  pop         ecx  
00411410  mov         dword ptr [ebp-8],ecx 
 m_value += 1;
00411413  mov         eax,dword ptr [this] 
00411416  mov         ecx,dword ptr [eax] 
00411418  add         ecx,1 
0041141B  mov         edx,dword ptr [this] 
0041141E  mov         dword ptr [edx],ecx 
 return 0;
00411420  xor         eax,eax 
}
00411422  pop         edi  
00411423  pop         esi  
00411424  pop         ebx  
00411425  mov         esp,ebp 
00411427  pop         ebp  
00411428  ret         4    
--- No source file -------------------------------------------------------------

同理我們看看第二條語句“call        CTestB::func (4110B9h”該語句是跳到地址4110B9h處,如下面的代碼:

004110B9  jmp         CTestB::func (411530h)

該語句是一條跳轉指令,同上,該指令會跳到正真的成員函數所在的內存地址處(00411530 ),代碼如下所示:

int CTestB::func(int param)
{
00411530  push        ebp  
00411531  mov         ebp,esp 
00411533  sub         esp,0CCh 
00411539  push        ebx  
0041153A  push        esi  
0041153B  push        edi  
0041153C  push        ecx  
0041153D  lea         edi,[ebp-0CCh] 
00411543  mov         ecx,33h 
00411548  mov         eax,0CCCCCCCCh 
0041154D  rep stos    dword ptr es:[edi] 
0041154F  pop         ecx  
00411550  mov         dword ptr [ebp-8],ecx 
 m_value += 1;
00411553  mov         eax,dword ptr [this] 
00411556  mov         ecx,dword ptr [eax] 
00411558  add         ecx,1 
0041155B  mov         edx,dword ptr [this] 
0041155E  mov         dword ptr [edx],ecx 
 return 0;
00411560  xor         eax,eax 
}
00411562  pop         edi  
00411563  pop         esi  
00411564  pop         ebx  
00411565  mov         esp,ebp 
00411567  pop         ebp  
00411568  ret         4    

小結一下:

            不同類中具有相同特征標的同名方法,編譯器是通過類的作用域操作符(::)來區分的,這樣的函數分別有自己的內存空間,這點可以從上面它們具有不同的內存地址可以得到證實。

            同一個類的所有實例對象都是使用內存中的同一組成員方法拷貝,所以類的成員方法不屬於任何一個對象,所有的對象共享內存中的同一份成員函數拷貝;因此一個類的對象只包含類聲明中的數據部分,包括一個VTBL指針數據(如果該類中有virtual成員函數的話),那么如果某個對象要使用類聲明中的某個成員函數怎么辦呢?這個就涉及到C++編譯中引入的this指針了,這this指針就是指向調用成員函數的對象內存的地址;我們以程序為證:

我修改過了上面的main函數,如下:

主要看下面用紅色標志的地方,我用同一類聲明了兩個對象testA和testA2,然后我們再看看VS2005編譯是如何處理對象調用函數的,還是看VS給出的匯編代碼吧。

int _tmain(int argc, _TCHAR* argv[])
{
 float x = 2.0;
 CTestA testA;
 CTestA testA2;
 CTestB testB;

 testA.func(1);
 testA2.func(4);
 testA.func(x);
 testA.func(2.0f);
 //testA.func(2.0); //error ambiguous call

 testB.func(2);
 return 0;
}

匯編代碼如如下:

這次我只給出有關這兩個對象的匯編代碼片段:

 testA.func(1);
0041167D  push        1    
0041167F  lea         ecx,[ebp-24h] 
00411682  call        CTestA::func (4110F0h) 
 testA2.func(4);
00411687  push        4    
00411689  lea         ecx,[ebp-34h] 
0041168C  call        CTestA::func (4110F0h)

可以看出一個類的不同對象,調用的成員函數func地址是一樣的,這就證明了我們上面的結論 ——“一個類的不同對象是共享同內存處的同一組函數拷貝”。

那么我們接下來就是要證明的就是對象只包含數據不包含函數而且成員函數是通過this指針來區別同一類的不同對象的????

我們還是看匯編代碼吧,我們在上面的兩條紅色標志的代碼處分別按下F11兩次,第一次按F11是跳到如下語句

004110F0  jmp         CTestA::func (4113F0h)

第二次按F11就會到達成員函數func所在的內存處,匯編代碼如下:

int CTestA::func(int param)
{
004113F0  push        ebp  
004113F1  mov         ebp,esp 
004113F3  sub         esp,0CCh 
004113F9  push        ebx  
004113FA  push        esi  
004113FB  push        edi  
004113FC  push        ecx  
004113FD  lea         edi,[ebp-0CCh] 
00411403  mov         ecx,33h 
00411408  mov         eax,0CCCCCCCCh 
0041140D  rep stos    dword ptr es:[edi] 
0041140F  pop         ecx  
00411410  mov         dword ptr [ebp-8],ecx 
 m_value += 1;
00411413  mov         eax,dword ptr [this] 
00411416  mov         ecx,dword ptr [eax] 
00411418  add         ecx,1 
0041141B  mov         edx,dword ptr [this] 
0041141E  mov         dword ptr [edx],ecx 
 return 0;
00411420  xor         eax,eax 
}

看到沒有在操作具體數據時用到了this指針,我們用VS提供的QuichWatch功能,就看到this的地址,以及它所指向的對象的值:

              this值為0x0012ff44

              this所指向的對象值為{m_value=0 m_float=0.00000000}

我們還沒有證明我們剛才所說的結論,還必須要看一下this所指向的下一個對象的地址,也就說在用下一個對象(testA2)調用成員函數時的this指針的值。

還是看匯編代碼比較有說服力:

int CTestA::func(int param)
{
004113F0  push        ebp  
004113F1  mov         ebp,esp 
004113F3  sub         esp,0CCh 
004113F9  push        ebx  
004113FA  push        esi  
004113FB  push        edi  
004113FC  push        ecx  
004113FD  lea         edi,[ebp-0CCh] 
00411403  mov         ecx,33h 
00411408  mov         eax,0CCCCCCCCh 
0041140D  rep stos    dword ptr es:[edi] 
0041140F  pop         ecx  
00411410  mov         dword ptr [ebp-8],ecx 
 m_value += 1;
00411413  mov         eax,dword ptr [this] 
00411416  mov         ecx,dword ptr [eax] 
00411418  add         ecx,1 
0041141B  mov         edx,dword ptr [this] 
0041141E  mov         dword ptr [edx],ecx 
 return 0;
00411420  xor         eax,eax 
}
代碼和上面是一摸一樣的,不同的就是this值,使用同樣的方法查看this值:

             this值為0x0012ff34

可以明顯的看出此處的this值不等於上面的this值(0x0012ff34 != 0x0012ff44),這就證明了我們的編譯器使用this指針來區別同一類的不同對象,已經對象只包含類聲明時的數據(注意如果是static數據成員 就不會包含在對象中;只有類聲明中包含virtual成員函數是對象中才有vtbl指針,這些留給讀者可以自己去驗證!)

轉載於:https://my.oschina.net/N3verL4nd/blog/866930


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM