執行下面一段代碼html
#include "stdafx.h" void swap(int *a ,int *b) { int *tmp; tmp=a; a=b; b=tmp; } int& swap2(int& a ,int& b) { int tmp; tmp=a; a=b; b=tmp; return a; } void main() { int a,b; a=100; b=200; printf("a:%d,b:%d,&a:%d,&b:%d\n",a,b,&a,&b); swap2(a,b); printf("a:%d,b:%d,&a:%d,&b:%d\n",a,b,&a,&b); //爲何兩次&a,&b的值相同? }
結果是c++
a:100,b:200,&a:3210136,&b:3210124 a:200,b:100,&a:3210136,&b:3210124
值交換了,可是爲何&a,&b沒有交換呢?引用傳遞不是會傳遞參數的地址嗎?爲何參數的值改變了,參數的地址卻沒有變呢?函數
看到了這片文章,可是卻苦於對彙編指令不瞭解,琢磨了半天仍是感受很模糊。如下是引用這篇文章的內容spa
————————————分割線————————————指針
1、 函數參數傳遞機制的基本理論code
函數參數傳遞機制問題在本質上是調用函數(過程)和被調用函數(過程)在調用發生時進行通訊的方法問題。基本的參數傳遞機制有兩種:值傳遞和引用傳遞。如下討論稱調用其餘函數的函數爲主調函數,被調用的函數爲被調函數。htm
值傳遞(passl-by-value)過程當中,被調函數的形式參數做爲被調函數的局部變量處理,即在堆棧中開闢了內存空間以存放由主調函數放進來的實參的值,從而成爲了實參的一個副本。值傳遞的特色是被調函數對形式參數的任何操做都是做爲局部變量進行,不會影響主調函數的實參變量的值。blog
引用傳遞(pass-by-reference)過程當中,被調函數的形式參數雖然也做爲局部變量在堆棧中開闢了內存空間,可是這時存放的是由主調函數放進來的實參變量的地址。被調函數對形參的任何操做都被處理成間接尋址,即經過堆棧中存放的地址訪問主調函數中的實參變量。正由於如此,被調函數對形參作的任何操做都影響了主調函數中的實參變量。進程
2、 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時,是以值傳遞的方式將指針變量p一、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 inC++》一書在第10章有比較好的闡述。四是一個顯而易見的結論,若是在被調函數中返回局部變量的地址是毫無心義的;由於局部變量存於堆棧中,調用結束後堆棧將被清理,這些地址就變得無效了。
3、 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將不能出如今"="的左邊。
4、 結束語
本文論述了在 C 和 c++ 中函數調用的參數傳遞機制;同時附帶說明了函數返回值的一些問題。本文示例使用的是VC++6.0。
可見值傳遞是傳輸了要傳遞的變量的一個副本,因此改變這個副本不會對調用函數形成影響,可是這個被調用函數通常有一個有用的返回值,也就是你用某個東西,在使用過程當中,也許改變了它,可是時候後,你又保持原樣給了人家。好比給你一個打好節的絲巾,你使用時換了另外一種樣式,照了像,還別人的時候,又按照人家的借你的樣子還給人家,而這個照片就是須要獲得的東西(相似返回值)。
而引用,就是將要傳遞的變量的地址傳到了被調用函數中,若是在被調用函數中改變,那麼就會在調用函數中改變。好比你借了人家布,若是你剪裁了不一樣的樣式,那麼還人家的樣子就是你剪裁後的樣子。通常c++可使用值傳遞和引用傳遞,後者更多。由於這樣不用另外在堆棧中開闢空間,而值傳遞就須要另外的開闢空間,對內存有必定的浪費。通常c中只使用值傳遞。
另外關於存儲數據方面,通常是將局部變量,函數返回地址,函數參數放到堆棧中,而函數返回值通常放到寄存器中,爲的是方便中斷,若是有零時中斷就能夠直接從寄存器中處理,不用再進行壓棧出棧操做。