程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C/C++的參數傳遞機制

C/C++的參數傳遞機制

編輯:C++入門知識

C/C++的參數傳遞機制


近來公司招人較多,由此面試了非常多的C++程序員。面試時,我都會問到參數傳遞的相關問題,尤其側重指針。因為指針畢竟是C/C++最重要的一個優勢(在某種情況下也可以說是劣勢)。但其結果是,1/3的人基本上講錯了,1/3的知其然卻不知其所以然。所以我覺得有必要把這些知識點梳理下,分享出來。(下面的討論都是基於VS和GCC的默認編譯方式,其他特殊編譯方式不在本文作用范圍內。)   C/C++函數參數的傳遞方式有三種:值傳遞(pass by value)、指針傳遞(pass bypointer)、引用傳遞(pass by reference)。   C/C++函數參數的傳遞通道是通過堆棧傳遞,默認遵循__cdecl(C聲明方式),參數由調用者從右往左逐個壓入堆棧,在函數調用完成之後再由調用者恢復堆棧。(Win32API遵循stdcall傳參規范的,不在本文討論范圍)   下面是測試代碼     void Swap(__int64* _pnX, __int64* _pnY) {     __int64 nTemp = *_pnX;     *_pnX = *_pnY;     *_pnY = nTemp; }   void Swap(__int64& _nX, __int64& _nY) {     __int64 nTemp = _nX;     _nX = _nY;     _nY = nTemp; }   void SetValue(__int64 _nX) {     __int64 nTemp = _nX; }   // Test001 void GetMemory(__int64* _pBuff) {     _pBuff = new __int64[4]; }   // Test002 void GetMemory(__int64** _ppBuff) {     *_ppBuff = new __int64[4]; }   int _tmain(int argc, _TCHAR* argv[]) {     __int64 nA = 0x10;     __int64 nB = 0x20;       // Test to pass by pointer     Swap(&nA, &nB);       // Test to pass by reference     Swap(nA, nB);       // Test to pass by value     SetValue(nA);       // Test the pointer that points the pointer     __int64* _pArray = NULL;     GetMemory(&_pArray);     delete[] _pArray;     _pArray = NULL;       // Test the pointer      GetMemory(_pArray);       return 0; }   指針傳遞和引用傳遞     // 下面看一下對應的反匯編的代碼(VS版) __int64 nA = 0x10; 0041370E  mov         dword ptr [nA],10h  00413715  mov         dword ptr [ebp-8],0  __int64 nB = 0x20; 0041371C  mov         dword ptr [nB],20h  00413723  mov         dword ptr [ebp-18h],0    // Test to pass by pointer Swap(&nA, &nB); 0041372A  lea         eax,[nB]  0041372D  push        eax   0041372E  lea         ecx,[nA]  00413731  push        ecx   00413732  call        Swap (4111E5h)  00413737  add         esp,8    // Test to pass by reference Swap(nA, nB); 0041373A  lea         eax,[nB]  0041373D  push        eax   0041373E  lea         ecx,[nA]  00413741  push        ecx   00413742  call        Swap (4111E0h)  00413747  add         esp,8    // GCC版    0x00401582 <+30>:    lea    eax,[esp+0x18]    0x00401586 <+34>:    mov    DWORD PTR [esp+0x4],eax    0x0040158a <+38>:    lea    eax,[esp+0x1c]    0x0040158e <+42>:    mov    DWORD PTR [esp],eax    0x00401591 <+45>:    call   0x401520 <Swap(int*, int*)>    0x00401596 <+50>:    lea    eax,[esp+0x18]    0x0040159a <+54>:    mov    DWORD PTR [esp+0x4],eax    0x0040159e <+58>:    lea    eax,[esp+0x1c]    0x004015a2 <+62>:    mov    DWORD PTR [esp],eax    0x004015a5 <+65>:    call   0x401542 <Swap(int&, int&)>   通過上面的反匯編代碼,我們可以看出指針傳遞和引用傳遞在機制是一樣的,都是將指針值(即地址)壓入棧中,調用函數,然後恢復棧。Swap(nA, nB)和Swap(&nA, &nB);在實際上的匯編代碼也基本上一模一樣,都是從棧中取出地址來。由此可以看出引用和指針在效率上是一樣的。這也是為什麼指針和引用都可以達到多態的效果。指針傳遞和引用傳遞其實都是改變的地址指向的內存上的值來達到修改參數的效果。   值傳遞   下面是值傳遞對應的反匯編代碼     // Test to pass by value SetValue(nA); 0041374A  mov         eax,dword ptr [ebp-8] 0041374D  push        eax  0041374E  mov         ecx,dword ptr [nA] 00413751  push        ecx  00413752  call        SetValue (4111EAh) 00413757  add         esp,8 因為我的機器是32位的CPU,從上面的匯編代碼可以看64Bit的變量被分成2個32Bit的參數壓入棧中。這也是我們常說的,值傳遞會形成一個拷貝。如果是一個自定義的結構類型,並且有很多參數,那麼如果用值傳遞,這個結構體將被分割為非常多個32Bit的逐個拷貝到棧中去,這樣的參數傳遞效率是非常慢的。所以結構體等自定義類型,都使用引用傳遞,如果不希望別人修改結構體變量,可以加上const修飾,如(const MY_STRUCT&  _value);   下面來看一下Test001函數對應的反匯編代碼的參數傳遞     __int64* _pArray = NULL; 004137E0  mov         dword ptr [_pArray],0  // Test the pointer  GetMemory(_pArray); 00413812  mov         eax,dword ptr [_pArray]  00413815  push        eax   00413816  call        GetMemory (411203h)  0041381B  add         esp,4    從上面的匯編代碼可以看出,其實是0被壓入到棧中作為參數,所以GetMemory(_pArray)無論做什麼事,其實都與指針變量_pArray無關。GetMemory()分配的空間是讓棧中的臨時變量指向的,當函數退出時,棧得到恢復,結果申請的空間沒有人管,就產生內存洩露的問題了。《C++ Primer》將參數傳遞分為引用傳遞和非引用傳遞兩種,非引用傳遞其實可以理解為值傳遞。這樣看來,指針傳遞在某種意義上也是值傳遞,因為傳遞的是指針的值(1個4BYTE的值)。值傳遞都不會改變傳入實參的值的。而且普通的指針傳遞其實是改變的指針變量指向的內容。   下面再看一下Test002函數對應的反匯編代碼的參數傳遞   __int64* _pArray = NULL; 004137E0  mov         dword ptr [_pArray],0  GetMemory(&_pArray); 004137E7  lea         eax,[_pArray]  004137EA  push        eax   004137EB  call        GetMemory (4111FEh)    004137F0  add         esp,4    從上面的匯編代碼lea eax,[_pArray] 可以看出,_pArray的地址被壓入到棧中去了。   然後看一看GetMemory(&_pArray)的實現匯編代碼。      0x0040159b <+0>:        push   ebp      0x0040159c <+1>:        mov    ebp,esp      0x0040159e <+3>:        sub    esp,0x18      0x004015a1 <+6>:        mov    DWORD PTR [esp],0x20      0x004015a8 <+13>:        call   0x473ef0 <_Znaj>      0x004015ad <+18>:        mov    edx,DWORD PTR [ebp+0x8]      0x004015b0 <+21>:        mov    DWORD PTR [edx],eax      0x004015b2 <+23>:        leave       0x004015b3 <+24>:        ret     藍色的代碼是分配臨時變量空間,然後調用分配空間函數分配空間,得到的空間指針即eax.   然後紅色的匯編代碼即從ebp+0x8的棧上取到上面壓入棧中的參數_pArray的地址.   mov DWORD PTR [edx],eax即相當於把分配的空間指針eax讓edx指向,也即讓_pArray指向分配的空間eax.   總之,無論是哪種參數傳遞方式,參數都是通過棧上的臨時變量來間接參與到被調用函數的。指針作為參數,其本身的值是不可能被改變的,能夠改變的是其指向的內容。引用是通過指針來實現的,所以引用和指針在效率上一樣的。

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