取地址符&做函數形參?
C語言強大且危險
引入
這個問題花去了整整一天的研究。
先看一段嚴蔚敏的《數據結構》中棧的例程:
這里面的&S第一遍看的時候想當然的認為是取了SqStack結構體的S的地址,沒有細想。然后又看到了這句。
// platform: VC++6.0
Status Pop(SqStack &S, SElemType &e); //取地址符?
1
2
我開始突然發現,這真的是取地址符嗎,對照了我自己寫的程序,仔細推敲發現不太對。
仔細看這里的&e,如果這是個整型的棧,那么SElemType就是int,那么這里就等於:
Status Pop(SqStack &S, int &e); //很奇怪
1
類比的疑問
我們都知道:
int a,b; /* 定義了兩個整型的指針 */
int **a, *b; / 定義了整型指針的指針 */
1
2
那么難道說是
int &e; // 定義了以一個整型數為地址的變量e?
1
仔細看下接下來的函數定義:
顯然這里可以看出由於top指針指向的是SElemType類型,所以e是SElemType類型的。所以以上類比顯然是不對的。
C/C++中的引用參數
查找了很多的資料發現,這個實際上是C++里的形參符號,必須要在跟在數據類型的后面使用。在函數內部對形參的操作都等同於直接操作原變量。
先說形參和實參
學過C語言的都知道,一個經典的例子是關於寫一個交換兩個變量a,b的值的函數:
// “形參不等於實參”的經典錯誤示范
void swap(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
void main()
{
int a = 1, b = 2;
swap(a,b)l
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
我們都知道把a,b作為形參傳入時,會臨時的分配形參空間讀取實參a,b的值存入,這里的形參a,b實際地址是不同於原來的實參。
形象的說,實參a是一份講義,你在調用函數的時候,函數就像學生一樣去要講義(傳遞的實參)。函數向系統要了張白紙(棧區空間),然后把這篇文章抄了一份拿去用了,取名也叫作a。然后他怎么修改都不會
繼續准確點說, 在程序運行的時候會分配一個全局區,我們這里說的a,b實際上屬於全局變量,存儲在全局區,也有的地方叫做靜態區。而這里的形參存儲在棧區,僅僅是保存了全局量的值,所以所有對形參a,b的操作都和靜態區的a,b無關。
這里實參傳遞給形參的過程叫做值傳遞。
附:C/C++程序的內存分配知識
一個由C/C++編譯的程序占用的內存分為以下幾個部分 :
1、棧區(stack)― 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧。
2、堆區(heap) ― 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表。這個空間是公用的,如果沒有釋放會使得可用堆區空間變小,最好在申請后手動釋放。
3、全局區(靜態區)(static)―,全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。 - 程序結束后由系統釋放
4、文字常量區 ―常量字符串就是放在這里的。 程序結束后由系統釋放
5、程序代碼區―存放函數體的二進制代碼。
所以我們可以理解為,這里的&e是為了說明e變量不是僅僅的把值傳遞進了函數內部。
那怎么通過函數操作函數外部的參數呢?
根據C語言學習中標准解法,一是將實參的地址傳遞進函數中函數中,通過地址直接操作原變量的值;二是利用函數本身的返回。
// 利用指針的經典解法
void swap(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void main()
{
int a = 1, b = 2;
swap(&a,&b);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
必須先弄清楚各種地址
要理清引用參數的使用和原理,明白這個&符號和指針的區別,先必須搞清楚數據的地址。
在《操作系統》中,可以得知三種地址的區別:邏輯地址、線性地址和物理地址。
關於這三者的區別可以看這里:
操作系統邏輯地址、線性地址和物理地址
http://www.cnblogs.com/dirichlet/archive/2011/03/18/1987746.html
或者這里
http://blog.csdn.net/geekwill/article/details/52449855
這里用圖簡單通俗的說下,為了通俗易懂,嚴格上並不准確:
我們的程序在操作系統中運行的時候,會給我們的程序(進程)在內存中分配一些空間。為了方便說明,這里假設內存是16位地址(實際上32位地址支持4G內存),我們可以看到a的物理地址是0x23。
然后0x2300是什么呢,這個是進程數據段的首地址,一般我們習慣叫做程序運行的入口地址。
像上面的圖所示,我們通過&a把a的邏輯地址傳遞進了函數swap中,然后swap函數通過*a找到a的物理地址,這個是操作系統完成的,其中會經過一些過程,需要先變換為線性地址。
那么我們可以總結:
實際上在C語言中,使用&取地址符取出的是變量的[邏輯地址],就算用匯編進行操作也是一樣。變量的物理地址只有操作系統知道,實際上邏輯地址和物理地址都是32位整數(32位機)。兩個不同進程,就算邏輯地址一樣,實際的物理地址也不同。
這里關於各種變量的內存地址相關可以參考:
C語言內存地址基礎
http://blog.jobbole.com/44845/
關於C語言的函數調用過程更加深度嚴謹(也更難懂)的知識,牆裂推薦這篇文章:
深入理解C語言的函數調用過程
http://blog.chinaunix.net/uid-23069658-id-3981406.html
通過引用傳遞和通過指針傳遞?
之前的兩個例子,分別用常規的值傳遞和指針的傳遞實現數據交換的過程看起來不同,其實都是差不多的。實質上都是值傳遞。
第一個例子的執行過程:
第二個例子的執行過程:
可以看出實際上利用指針的方法也只是把a,b的邏輯地址作為一個整數通過值傳遞到形參里存儲起來了,值傳遞的內容是a,b的邏輯地址。這兩種方式都需要額外的開辟棧區的內存,而且指針操作是易錯且不安全的。
下面是通過引用參數完成的交換過程。
// 引用參數實現交換
void swap(int &a, int &b){
int temp;
temp = a;
a = b;
b = temp;
}
// Using main to test
void main(){
void swap(int&, int&);
int a = 1, b = 2;
swap(a,b);
printf("%d %d\n",a,b);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
有些文章說道,通過引用的方式傳遞給函數的是變量的地址,這種方式叫做地址傳遞方式,還提到這是和“值傳遞”十分不同的方式。
有些書說道:“引用實際上是取了個‘別名’”
還有的書和文章說道引用是比通過指針傳遞更加高效的方式,因為不需要開辟新的內存空間用來拷貝實參的地址。
真的嗎?
引用實現原理的討論
先討論引用實現的系列文章,大佬們講得比較透徹,而且論據豐富。
c++中的引用的使用原理以及使用實例
http://blog.csdn.net/ztz0223/article/details/1639305
C++ 引用 參數傳遞 機制【強烈推薦】
http://blog.csdn.net/huqinweI987/article/details/50769096
C++引用的本質與修改引用的方法
http://blog.csdn.net/huqinweI987/article/details/24874403
舉例剖析C++中引用的本質及引用作函數參數的使用
http://www.jb51.net/article/80911.htm
如果不想看干貨長文的就看下下面的通俗簡短討論吧。
我們看下下面這段小程序:
int i = 0;
int &j = i; // 設j為i的一個引用
j = 10; // 將j試着改變為10
int *p = &j; // 觀察引用的取地址操作
1
2
3
4
匯編(偽匯編)解析如下:
;int i = 0;
mov dword ptr [i],0; // i的內容置為0;
;int &j = i;
lea eax, [i]; // 將i的地址放入eax寄存器
mov dword ptr[j],eax; // 將i的地址傳入j的內容
; j = 10;
mov eax, dword ptr[j]; // 取j的內容(i的地址)放入eax
mov dword ptr [eax], 0Ah; // 將eax地址指向修改為10;
;int *p = &j;
mov eax,dword ptr [j] // 將j的內容傳給eax
mov dword ptr [p],eax // 把eax內容傳入p的內容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
實際上,通過對匯編的分析可以看出:
“引用是一個別名”的說法並不准確,實際上實現過程中引用也可以看成是一種指針,實際上引用變量存儲的就是引用對象的地址,也要占用內存空間(和指針占用大小不同),只不過C++的標准規定了引用初始化完畢之后,對引用的操作就等於是對實際對象的操作。
雖然引用可以看做特殊的指針,對引用的操作會被編譯器解釋成對地址指向的目標的操作。但和*p這種取指針指向對象的方式不同,這種方式不會開辟臨時空間存儲指針指向的對象。如果指向對象很大,操作重復數很多,這個差異就會對性能有十分大的影響。
引用的本身值,即引用對象的地址不可以像指針變量一樣修改,對引用的操作只會解釋成對引用對象的操作,可以理解引用變量是一個靜態的指針。
對第2條的解釋,關於指針操作拷貝副本和引用節省空間的詳細解釋可以看上面的文章—— C++ 引用 參數傳遞 機制【強烈推薦】。
轉載來自:http://m.blog.csdn.net/JayRoxis/article/details/73060770