C/C++中的函數(shù)參數(shù)傳遞機(jī)制
一、 函數(shù)參數(shù)傳遞機(jī)制的基本理論 函數(shù)參數(shù)傳遞機(jī)制問題在本質(zhì)上是調(diào)用函數(shù)(過程)和被調(diào)用函數(shù)(過程)在調(diào)用發(fā)生時(shí)進(jìn)行通信的方法問題?;镜膮?shù)傳遞機(jī)制有兩種:值傳遞和引用傳遞。以下討論稱調(diào)用其他函數(shù)的函數(shù)為主調(diào)函數(shù),被調(diào)用的函數(shù)為被調(diào)函數(shù)。 值傳遞(passl-by-value)過程中,被調(diào)函數(shù)的形式參數(shù)作為被調(diào)函數(shù)的局部變量處理,即在堆棧中開辟了內(nèi)存空間以存放由主調(diào)函數(shù)放進(jìn)來的實(shí)參的值,從而成為了實(shí)參的一個(gè)副本。值傳遞的特點(diǎn)是被調(diào)函數(shù)對形式參數(shù)的任何操作都是作為局部變量進(jìn)行,不會影響主調(diào)函數(shù)的實(shí)參變量的值。 引用傳遞(pass-by-reference)過程中,被調(diào)函數(shù)的形式參數(shù)雖然也作為局部變量在堆棧中開辟了內(nèi)存空間,但是這時(shí)存放的是由主調(diào)函數(shù)放進(jìn)來的實(shí)參變量的地址。被調(diào)函數(shù)對形參的任何操作都被處理成間接尋址,即通過堆棧中存放的地址訪問主調(diào)函數(shù)中的實(shí)參變量。正因?yàn)槿绱?,被調(diào)函數(shù)對形參做的任何操作都影響了主調(diào)函數(shù)中的 實(shí)參變量。
二、 C語言中的函數(shù)參數(shù)傳遞機(jī)制 在C語言中,值傳遞是唯一可用的參數(shù)傳遞機(jī)制。但是據(jù)筆者所知,由于受指針變量作為函數(shù)參數(shù)的影響,有許多朋友還認(rèn)為這種情況是引用傳遞。這是錯(cuò)誤的。請看下面的代碼: 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) } 函數(shù)swap以兩個(gè)指針變量作為參數(shù),當(dāng)main()調(diào)用swap時(shí),是以值傳遞的方式將指針變量p1、p2的值(也就是變量a、b的地址)放在了swap在堆棧中為形式參數(shù)x、y開辟的內(nèi)存單元中。這一點(diǎn)從以下的匯編代碼可以看出(注釋是筆者加的): 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] ;參數(shù)p2的值進(jìn)棧 004010A5 push edx 004010A6 mov eax,dword ptr [ebp-0Ch] ;參數(shù)p1的值進(jìn)棧 004010A9 push eax 004010AA call @ILT+15(swap) (00401014) ;調(diào)用swap函數(shù) 004010AF add esp,8 ;清理堆棧中的參數(shù) 17: } 閱讀上述代碼要注意,INTEL80x86系列的CPU對堆棧的處理是向下生成,即從高地址單元向低地址單元生成。從上面的匯編代碼可知,main()在調(diào)用swap之前,先將實(shí)參的值按從右至左的順序壓棧,即先p2進(jìn)棧,再p1進(jìn)棧。調(diào)用結(jié)束之后,主調(diào)函數(shù)main()負(fù)責(zé)清理堆棧中的參數(shù)。Swap 將使用這些進(jìn)入堆棧的變量值。下面是swap函 數(shù)的匯編代碼: 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;經(jīng)由ecx將*p1置入temp變量的內(nèi) ;存單元。以下類似 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函數(shù)。從這些匯編代碼分析,這里我們可以得到以下幾點(diǎn): 1. 進(jìn)程的堆棧存儲區(qū)是主調(diào)函數(shù)和被調(diào)函數(shù)進(jìn)行通信的主要區(qū)域。 2. C語言中參數(shù)是從右向左進(jìn)棧的。 3. 被調(diào)函數(shù)使用的堆棧區(qū)域結(jié)構(gòu)為: 局部變量(如temp) 返回地址 函數(shù)參數(shù) 低地址 高地址 4. 由主調(diào)函數(shù)在調(diào)用后清理堆棧。 5. 函數(shù)的返回值一般是放在寄存器中的。 這里尚需補(bǔ)充說明幾點(diǎn):一是參數(shù)進(jìn)棧的方式。對于內(nèi)部類型,由于編譯器知道各類型變量使用的內(nèi)存大小故直接使用push指令;對于自定義的類型(如structure),采用從源地址向目的(堆棧區(qū))地址進(jìn)行字節(jié)傳送的方式入棧。二是函數(shù)返回值為什么一般放在寄存器中,這主要是為了支持中斷;如果放在堆棧中有可能因?yàn)橹袛喽桓采w。三是函數(shù)的返回值如果很大,則從堆棧向存放返回值的地址單元(由主調(diào)函數(shù)在調(diào)用前將此地址壓棧提供給被調(diào)函數(shù))進(jìn)行字節(jié)傳送,以達(dá)到返回的目的。對于第二和第三點(diǎn),《Thinking in C++》一書在第10章有比較好的闡述。四是一個(gè)顯而易見的結(jié)論,如果在被調(diào)函數(shù)中返回局部變量的地址是毫無意義的;因?yàn)榫植孔兞看嬗诙褩V?,調(diào)用結(jié)束后堆棧將被清理,這些地址就變得無效了。
三、 C++語言中的函數(shù)參數(shù)傳遞機(jī)制 C++既有C的值傳遞又有引用傳遞。在值傳遞上與C一致,這里著重說明引用傳遞。如本文前面所述,引用傳遞就是傳遞變量的地址到被調(diào)函數(shù)使用的堆棧中。在C++中聲明引用傳遞要使用"&"符號,而調(diào)用時(shí)則不用。下面的代碼是使用引用傳遞的swap2函數(shù)和main函數(shù): 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); } 此時(shí)函數(shù)swap2將接受兩個(gè)整型變量的地址,同時(shí)返回一個(gè)其中的一個(gè)。而從main函數(shù)中對swap2的調(diào)用swap2(a, b)則看不出是否使用引用傳遞,是否使用引用傳遞,是由swap2函數(shù)的定義決定的。以下是main函數(shù)的匯編代碼: 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) ;調(diào)用swap函數(shù) 004010A3 add esp,8 ;清理堆棧中的參數(shù) 15: } 可以看出,main函數(shù)在調(diào)用swap2之前,按照從右至左的順序?qū)和a的偏移地 址壓棧,這就是在傳遞變量的地址。此時(shí)swap2函數(shù)的匯編代碼是: 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函數(shù)的匯編代碼是一樣的。這是因?yàn)榍懊娴膕wap函數(shù)接受指針變量,而指針變量的值正是地址。所以,對于這里的swap2和前面的swap來講,堆棧中的函數(shù)參數(shù)存放的都是地址,在函數(shù)中操作的方式是一致的。但是,對swap2來說這個(gè)地址是主調(diào)函數(shù)通過將實(shí)參變量的偏移地址壓棧而傳遞進(jìn)來的--這 是引用傳遞;而對swap來說,這個(gè)地址是主調(diào)函數(shù)通過將實(shí)參變量的值壓棧而傳遞進(jìn)來的--這是值傳遞,只不過由于這個(gè)實(shí)參變量是指針變量所以其值是地址而已。 這里的關(guān)鍵點(diǎn)在于,同樣是地址,一個(gè)是引用傳遞中的變量地址,一個(gè)是值傳遞中的指針變量的值。我想若能明確這一點(diǎn),就不至于將C語言中的以指針變量作為函數(shù)參數(shù)的值傳遞情況混淆為引用傳遞了。 雖然x是一個(gè)局部變量,但是由于其值是主調(diào)函數(shù)中的實(shí)參變量的地址,故在swap2中返回這個(gè)地址是合法的。 c++ 中經(jīng)常使用的是常量引用,如將swap2改為: Swap2(const int& x; const int& y) 這時(shí)將不能在函數(shù)中修改引用地址所指向的內(nèi)容,具體來說,x和y將不能出現(xiàn)在"="的左邊。
四、 結(jié)束語 本文論述了在 C 和 c++ 中函數(shù)調(diào)用的參數(shù)傳遞機(jī)制;同時(shí)附帶說明了函數(shù)返回值的一些問題。本文示例使用的是VC++6.0。
|