轉載:http://blog.csdn.net/feiyinzilgd/archive/2010/02/09/5302369.aspx
對於C語言的參數傳遞都是值傳遞,當傳傳遞一個指針給函數的時,其實質上還是值傳遞,除非使用雙指針。
在講雙指針之前,還是先講講關於C語言函數調用的本質。
函 數調用操作包括從一塊代碼到另一塊代碼之間的雙向數據傳遞和執行控制轉移。數據傳遞通過函數參數和返回值來進行,包括局部變量的空間分配與回收,都是通過 棧來實現的。絕大多數CPU上的程序實現使用棧來支持函數調用操作。棧被用來傳遞函數參數、存儲返回信息、臨時保存寄存器原有值以備恢復以及用來存儲局部 數據。當函數A調用函數B的時候,會把A的變量和參數壓入到棧中,然后接着將B的變量和參數,局部變量壓入到棧中。然后當A調用B是,B其實是在棧中取得 A傳遞的參數和值,從而達到值傳遞的效果。
以一個交換2個數的值的函數調用為例。
void
swap ( int *a, int *b ){
int c;
c = *a;
*a = *b;
*b = c;
}
int
main(int argc, char **argv){
int a,b;
a = 16;
b = 32;
swap( &a, &b);
return ( a - b );
}
那么,這段代碼編譯成匯編語言之后,除了會有代碼段,數據段,堆棧,那么在調用的時候,會把main函數的參數變量壓入main函數的棧幀,然后接着會壓入swap函數的局部變量和參數
那么按照剛才上述理論,編譯成匯編語言以后,這個圖就是函數調用的時候內存形態。
有了上面的圖和理論基礎,再來討論雙重指針的問題。當定義的時候,只有一個*號的時候,我們叫它一級指針。**個星號的叫二級指針。
當我們使用一級指針的時候,我們試圖使用下述錯誤代碼來實現2個數交換
void
swap ( int *a, int *b ){
int *temp;
temp = NULL;
temp = a;
a = b;
b = temp;
}
int
main ( int argc, char **argv ){
int a,b;
a = 16;
b = 32;
swap(&a, &b);
return ( a - b );
}
這種方式按照理論上來說,是想通過調用swap函數,在swap函數內部,實現將交換&a,&b,即交換a和b的地址來達到目的。這樣絕對不可以。因為當把a,b的地址傳到swap函數之后,按照上述棧幀圖的結構來看,最終swap函數值通過棧指針來實現的,當swap使用的時候,還是把 a,b的地址復制到寄存器中才能運算。那么,大家也許就明白了,swap把a,b的地址復制到寄存器中,然后運算,相當於抱着a,b的副本跑了,然后去操作,這些所有針對a,b副本的操作管main函數中的a,b什么事? 當swap返回之后,這些寄存器或者是棧空間隨着swap的然會而釋放了,而 main函數的a,b沒發生任何變法。所以上述代碼是錯誤的,無法實現你想要的功能。
當我們用二級指針來實現上述功能的時候有就可以達到效果。
void
swap ( int **a, int **b ){
int *tmp = NULL;
tmp = *a;
*a = *b;
*b = tmp;
}
int
main ()
....
....
這個時候,你會發現就能實現達到交換的目的。
這就是雙指針神奇的功能,突破C語言傳值的概念。那么,雙指針是如何達到效果的呢?
當我們申明 **a之后,其實雙指針變量a其實已經存在了。那么在內存中的效果如下圖
那么,p中放的是中間橋梁bridge的地址&bridge,則*p就是中間橋梁bridge的內容即是目標操作數的地址&income,從而**p就是目標操作數
再來看這個圖,p就是這里**a種的a.當我們申明**p之后,p就已經存在了。其實這個bridge也已經存在了,那么我們要做的就是bridge中放 我們要操作的數的地址。也就是&incom;那么,其實這樣操作*bridge就是操作&incom也就是&b啊,這個一級指針 沒什么區別啊。
請注意,對於一級指針,我們要操作的是就是b,那么按照匯編語言的規則,就要把b放到寄存器或者棧中去操作,我們相當於復制了一個副本去操作,等我們操作完了,返回函數,這些寄存器,棧等都釋放了,main中什么也沒發生。但是如果我們用雙指針,二級指針就不一樣了。我們操作的是bridge.我們只是機械的復制一個bridge的內容到寄存器或者到棧中,而沒有實際的去復制imcom的內容,我們只是告訴bridge,你要指向一個叫incom的地址, 也就是說,bridge的內容就是incom的地址,即&incom,通過bridge這個中間橋梁,所以,就達到目的了。所以,雙指針讓參數傳遞具有穿透力。
雙指針主要用在但我們想向一個A函數傳遞參數的時候,但是我們希望在A內部對參數做任何修改都能保存起來,那么就是用雙指針吧。
舉個例子;
我們在做鏈表的時候,我們肯定希望在用一個函數creatLink(...)函數來增加鏈表節點。那么我們可以有2種方法來實現
第一種,用一級指針
typedef struct node{
...
...
}list;
node *create(list *l){
list *head;
head = l;
malloc...//為節點申請內存空間
...
...
//操作
return head;
}
int main(...){
...
...
list *listhead
createList(listhead);
....
//以后的任何操作,我們都要考慮,我們是否拿到的是鏈表頭指針,到底哪個是鏈表波的頭指針,我們是否要renturn下來返回鏈表頭指針??等。。。。
}
這樣做可以達到刪除增加節點的目的,但是,在任何情況下,我們的操作都得死死地抓住頭指針,也即是我們增加刪除節點后,任何對鏈表長度的修改,我們都要 鏈表頭指針返回,即 return head;所以,我們要通過這個函數最后獲得頭指針,抓住他,死死地抓住他,然后操作。
第二種方法:用雙指針,也即是二級指針。
typedef struct node{
...
...
}list;
void
create(list **l){
list *head;
head = l;
malloc...//為節點申請內存空間
...
...
//操作
}
int
main(...){
...
...
list *st
createList(st)
....
..
//以后的任何操作,不管是刪除還 是插入,我們不需要考慮,我們是否已經return head了,不需要,我們在任何情況下,對鏈表的操作都只需要使用st來完成,因為,st就是鏈表的頭指針,不變,因為在申明st的時候,已經為st分配 一個地址空間,它是存在的,一直存在,直到main函數結束
}