C/C++中的函數參數傳遞機制(zz)

來源:互聯網
上載者:User

一、 函數參數傳遞機制的基本理論
  函數參數傳遞機制問題在本質上是調用函數(過程)和被調用函數(過程)在調用發生時進行通訊的方法問題。基本的參數傳遞機制有兩種:值傳遞和引用傳遞。以下討論稱調用其他函數的函數為主調函數,被調用的函數為被調函數。
  值傳遞(passl-by-value)過程中,被調函數的形式參數作為被調函數的局部變數處理,即在堆棧中開闢了記憶體空間以存放由主調函數放進來的實參的值,從而成為了實參的一個副本值傳遞的特點是被調函數對形式參數的任何操作都是作為局部變數進行,不會影響主調函數的實參變數的值。
  引用傳遞(pass-by-reference)過程中,被調函數的形式參數雖然也作為局部變數在堆棧中開闢了記憶體空間,但是這時存放的是由主調函數放進來的實參變數的地址。被調函數對形參的任何操作都被處理成間接定址,即通過堆棧中存放的地址訪問主調函數中的實參變數。正因為如此,被調函數對形參做的任何操作都影響了主調函數中的實參變數。

二、 C語言中的函數參數傳遞機制

  在C語言中,值傳遞是唯一可用的參數傳遞機制。但是據筆者所知,由於受指標變數作為函數參數的影響,有許多朋友還認為這種情況是引用傳遞。這是錯誤的。請看下面的代碼:
int swap(int *x, int *y)
{
int temp;
temp = *x; *x = *y; *y = temp;
return temp;
}
void main()
{
int a = 1, b = 2;
int *p1 = &a;
int *p2 = &b;
swap(p1, p2)
}
  函數swap以兩個指標變數作為參數,當main()調用swap時,是以值傳遞的方式將指標變數p1、p2的值(也就是變數a、b的地址)放在了swap在堆棧中為形式參數x、y開闢的記憶體單元中。這一點從以下的彙編代碼可以看出(注釋是筆者加的):
22: void main()
23: {
……
……
13: int a = 1, b = 2;
00401088 mov dword ptr [ebp-4],1
0040108F mov dword ptr [ebp-8],2
14: int *p1 = &a;
00401096 lea eax,[ebp-4]
00401099 mov dword ptr [ebp-0Ch],eax
15: int *p2 = &b;
0040109C lea ecx,[ebp-8]
0040109F mov dword ptr [ebp-10h],ecx
16: swap(p1, p2);
004010A2 mov edx,dword ptr [ebp-10h] ;參數p2的值進棧
004010A5 push edx
004010A6 mov eax,dword ptr [ebp-0Ch] ;參數p1的值進棧
004010A9 push eax
004010AA call @ILT+15(swap) (00401014) ;調用swap函數
004010AF add esp,8 ;清理堆棧中的參數
17: }
  閱讀上述代碼要注意,INTEL80x86系列的CPU對堆棧的處理是向下產生,即從高地址單元向低地址單元產生。從上面的彙編代碼可知,main()在調用swap之前,先將實參的值按從右至左的順序壓棧,即先p2進棧,再p1進棧。調用結束之後,主調函數main()負責清理堆棧中的參數。Swap 將使用這些進入堆棧的變數值。下面是swap函數的彙編代碼:
14: void swap(int *x, int *y)
15: {
00401030 push ebp
00401031 mov ebp,esp ;ebp指向棧頂
……
……
16: int temp;
17: temp = *x;
4: int temp;
5: temp = *x;
00401048 mov eax,dword ptr [ebp+8] ;操作已存放在堆棧中的p1,將p1置入eax
0040104B mov ecx,dword ptr [eax] ;通過寄存器間址將*p1置入ecx
0040104D mov dword ptr [ebp-4],ecx;經由ecx將*p1置入temp變數的記憶體單元。以下類似
6: *x = *y;
00401050 mov edx,dword ptr [ebp+8]
00401053 mov eax,dword ptr [ebp+0Ch]
00401056 mov ecx,dword ptr [eax]
00401058 mov dword ptr [edx],ecx
7: *y = temp;
0040105A mov edx,dword ptr [ebp+0Ch]
0040105D mov eax,dword ptr [ebp-4]
00401060 mov dword ptr [edx],eax
8: return temp;
00401062 mov eax,dword ptr [ebp-4]
9: }
由上述彙編代碼基本上說明了C語言中值傳遞的原理,只不過傳遞的是指標的值而已。本文後面還要論述使用引用傳遞的swap函數。從這些彙編程式碼分析,這裡我們可以得到以下幾點:
  1. 進程的堆棧儲存區是主調函數和被調函數進行通訊的主要區域。
  2. C語言中參數是從右向左進棧的。
  3. 被調函數使用的堆棧地區結構為:
    局部變數(如temp)
    返回地址
    函數參數
    低地址
    高地址
  4. 由主調函數在調用後清理堆棧。
  5. 函數的傳回值一般是放在寄存器中的。
  這裡尚需補充說明幾點:一是參數進棧的方式。對於內部類型,由於編譯器知道各類型變數使用的記憶體大小故直接使用push指令;對於自訂的類型(如structure),採用從源地址向目的(堆棧區)地址進行位元組傳送的方式入棧。二是函數傳回值為什麼一般放在寄存器中,這主要是為了支援中斷;如果放在堆棧中有可能因為中斷而被覆蓋。三是函數的傳回值如果很大,則從堆棧向存放傳回值的地址單元(由主調函數在調用前將此地址壓棧提供給被調函數)進行位元組傳送,以達到返回的目的。對於第二和第三點,《Thinking in C++》一書在第10章有比較好的闡述。四是一個顯而易見的結論,如果在被調函數中返回局部變數的地址是毫無意義的;因為局部變數存於堆棧中,調用結束後堆棧將被清理,這些地址就變得無效了。

三、 C++語言中的函數參數傳遞機制
  C++既有C的值傳遞又有引用傳遞。在值傳遞上與C一致,這裡著重說明引用傳遞。如本文前面所述,引用傳遞就是傳遞變數的地址到被調函數使用的堆棧中。在C++中聲明引用傳遞要使用"&"符號,而調用時則不用。下面的代碼是使用引用傳遞的swap2函數和main函數:
int& swap2(int& x, int& y)
{
int temp;
temp = x;
x = y;
y = temp;
return x;
}

void main()
{
int a = 1, b = 2;
swap2(a, b);
}
  此時函數swap2將接受兩個整型變數的地址,同時返回一個其中的一個。而從main函數中對swap2的調用swap2(a, b)則看不出是否使用引用傳遞,是否使用引用傳遞,是由swap2函數的定義決定的。以下是main函數的彙編代碼:
11: void main()
12: {
……
……
13: int a = 1, b = 2;
00401088 mov dword ptr [ebp-4],1 ;變數a
0040108F mov dword ptr [ebp-8],2 ;變數b
14: swap2(a, b);
00401096 lea eax,[ebp-8] ;將b的位移地址送入eax
00401099 push eax ;b的位移地址壓棧
0040109A lea ecx,[ebp-4] ;將a的位移地址送入ecx

0040109D push ecx ;將a的位移地址壓棧
0040109E call @ILT+20(swap2) (00401019) ;調用swap函數
004010A3 add esp,8 ;清理堆棧中的參數
15: }
可以看出,main函數在調用swap2之前,按照從右至左的順序將b和a的位移地
址壓棧,這就是在傳遞變數的地址。此時swap2函數的彙編代碼是:
2: int& swap2(int& x, int& y)
3: {
00401030 push ebp
00401031 mov ebp,esp
……
……
4: int temp;
5: temp = x;
00401048 mov eax,dword ptr [ebp+8]
0040104B mov ecx,dword ptr [eax]
0040104D mov dword ptr [ebp-4],ecx
6: x = y;
00401050 mov edx,dword ptr [ebp+8]
00401053 mov eax,dword ptr [ebp+0Ch]
00401056 mov ecx,dword ptr [eax]
00401058 mov dword ptr [edx],ecx
7: y = temp;
0040105A mov edx,dword ptr [ebp+0Ch]
0040105D mov eax,dword ptr [ebp-4]
00401060 mov dword ptr [edx],eax
8: return x;
00401062 mov eax,dword ptr [ebp+8] ;返回x,由於x是外部變數的位移地
;址,故返回是合法的
9: }
  可以看出,swap2與前面的swap函數的彙編代碼是一樣的。這是因為前面的swap函數接受指標變數,而指標變數的值正是地址。所以,對於這裡的swap2和前面的swap來講,堆棧中的函數參數存放的都是地址,在函數中操作的方式是一致的。但是,對swap2來說這個地址是主調函數通過將實參變數的位移地址壓棧而傳遞進來的--這是引用傳遞;而對swap來說,這個地址是主調函數通過將實參變數的值壓棧而傳遞進來的--這是值傳遞,只不過由於這個實參變數是指標變數所以其值是地址而已。
  這裡的關鍵點在於,同樣是地址,一個是引用傳遞中的變數地址,一個是值傳遞中的指標變數的值。我想若能明確這一點,就不至於將C語言中的以指標變數作為函數參數的值傳遞情況混淆為引用傳遞了。
  雖然x是一個局部變數,但是由於其值是主調函數中的實參變數的地址,故在swap2中返回這個地址是合法的。
  c++ 中經常使用的是常量引用,如將swap2改為:
    Swap2(const int& x; const int& y)
  這時將不能在函數中修改引用地址所指向的內容,具體來說,x和y將不能出現在"="的左邊。

四、 結束語
   本文論述了在 C 和 c++ 中函數調用的參數傳遞機制;同時附帶說明了函數傳回值的一些問題。本文樣本使用的是VC++6.0。

 

可見值傳遞是傳輸了要傳遞的變數的一個副本,所以改變這個副本不會對調用函數造成影響,但是這個被調用函數一般有一個有用的傳回值,也就是你用某個東西,在使用過程中,也許改變了它,但是時候後,你又保持原樣給了人家。比如給你一個打好節的絲巾,你使用時換了另一種樣式,照了像,還別人的時候,又按照人家的借你的樣子還給人家,而這個照片就是需要得到的東西(類似傳回值)。

而引用,就是將要傳遞的變數的地址傳到了被調用函數中,如果在被調用函數中改變,那麼就會在調用函數中改變。比如你借了人家布,如果你剪裁了不同的樣式,那麼還人家的樣子就是你剪裁後的樣子。一般c++可以使用值傳遞和引用傳遞,後者更多。因為這樣不用另外在堆棧中開闢空間,而值傳遞就需要另外的開闢空間,對記憶體有一定的浪費。一般c中只使用值傳遞。

另外關於儲存資料方面,一般是將局部變數,函數返回地址,函數參數放到堆棧中,而函數傳回值一般放到寄存器中,為的是方便中斷,如果有零時中斷就可以直接從寄存器中處理,不用再進行壓棧出棧操作。

相關文章

聯繫我們

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