1. 程式人生 > >關於指標作為引數傳遞的一些探討

關於指標作為引數傳遞的一些探討

值傳遞, 指標傳遞?

這幾天在學習C過程中,在使用指標作為函式引數傳遞的時候出現了問題,根本不知道從何得解:原始碼如下:
    createNode(BinNode *tree,char *p)
    {
        tree = (BinNode *) malloc(sizeof(BinNode));
        tree->data = *p;
    }
該程式碼段的意圖是通過一個函式建立一個二叉樹的節點,然而在,呼叫該函式後,試圖訪問該節點結構體的成員時候,卻發生了記憶體訪問錯誤,到底問題出在哪兒呢?

一直不明白指標作為函式引數傳值的機制,翻開林銳的《高質量C/C++程式設計指南》,找到了答案。

    [如果函式的引數是一個指標,不要指望用該指標去申請動態記憶體]
    
原來問題出在C編譯器原理上:編譯器總是要為函式的每個引數製作臨時副本,指標引數tree的副本是 _tree,編譯器使 _tree = tree。如果函式體內的程式修改了_tree的內容,就導致引數tree的內容作相應的修改。這就是指標可以用作輸出引數的原因。
即上面的函式程式碼經過編譯後成為:
    createNode(BinNode *tree,char *p)
    {
        BinNode *_tree;
        _tree = tree;
        _tree = (BinNode *) malloc(sizeof(BinNode));
        _tree->data = *p;
    }
如果沒有
    _tree = (BinNode *) malloc(sizeof(BinNode));
這個語句,在函式體內修改了_tree的內容,將會導致引數tree的內容作相應的修改,因為它們指向相同的記憶體地址。


    _tree = (BinNode *) malloc(sizeof(BinNode));
這個句,系統重新分配記憶體給_tree指標,_tree指標指向了系統分配的新地址,函式體內修改的只是_tree的內容,對原tree所指的地址的內容沒有任何影響。因此,函式的引數是一個指標時,不要在函式體內部改變指標所指的地址,那樣毫無作用,需要修改的只能是指標所指向的內容。即應當把指標當作常量。

如果非要使用函式指標來申請記憶體空間,那麼需要使用指向指標的指標
    createNode(BinNode **tree,char *p)
    {
        *tree = (BinNode *) malloc(sizeof(BinNode));


    }
上面的是林銳的說法,目前來說不知道怎麼去理解,不過可以有另外的方案,通過函式返回值傳遞動態記憶體:
BinNode *createNode()
    {
        BinNode *tree;
        tree = (BinNode *) malloc(sizeof(BinNode));
        return tree;
    }
用函式返回值來傳遞動態記憶體,可行


這個倒還說得過去,因為函式返回的是一個地址的值,該地址就是申請的記憶體塊首地址。但是,這個容易和另外的一個忠告相混繞
[不要用return語句返回指向“棧記憶體”的指標,因為該記憶體在函式結束時自動消亡]

這裡區分一下靜態記憶體,棧記憶體和動態分配的記憶體(堆記憶體)的區別:
(1) 從靜態儲存區域分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如全域性變數,static變數。
(2) 在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。
(3) 從堆上分配,亦稱動態記憶體分配。程式在執行的時候用malloc或new申請任意多少的記憶體,程式設計師自己負責在何時用free或delete釋放記憶體。動態記憶體的生存期由我們決定,使用非常靈活,但問題也最多。

因此,試圖返回一個棧上分配的記憶體將會引發未知錯誤
char *GetString(void)
    {
        char p[] = "hello world";
        return p; // 編譯器將提出警告
    }失敗,不可行

p是在棧上分配的記憶體,函式結束後將會自動釋放,p指向的記憶體區域內容不是"hello world",而是未知的內容
如果是返回靜態儲存的記憶體呢:
    char *GetString(void)
    {
        char *p = "hello world";
        return p;
    }
這裡“hello world”是常量字串,位於靜態儲存區,它在程式生命期內恆定不變。無論什麼時候呼叫GetString,它返回的始終是同一個“只讀”的記憶體塊。


[參考:林銳《高質量C/C++程式設計指南》]

補充:

void pointer(int *p)
{
  int a = 11;
  printf("\nthe p is %p , addr is %d, *p is %d",p , &p, *p);
  *p =11;
  printf("\nthe p is %p , addr is %d, *p is %d",p , &p, *p);
  p = &a;
  printf("\nthe p is %p , addr is %d, *p is %d",p , &p, *p);
}


int main()
{
 int b =22;
 int *p = &b;


 printf("\nthe p is %p , addr is %d, *p is %d",p , &p, *p);
 pointer(p);
 printf("\nthe p is %p , addr is %d, *p is %d",p , &p, *p);
}


the p is 0xbfd46498 , addr is -1076599652, *p is 22
the p is 0xbfd46498 , addr is -1076599680, *p is 22
the p is 0xbfd46498 , addr is -1076599680, *p is 11
the p is 0xbfd4646c , addr is -1076599680, *p is 11
the p is 0xbfd46498 , addr is -1076599652, *p is 11

1、例子中,指標p的拷貝傳入了方法中(其地址變了,說明是另一變數;值和指向的記憶體塊資料沒變)

2、將p的拷貝視作p1,p1改變了其所指向的記憶體塊的值為11

3、p1的值改變為a的地址,即p1指向a,此時p1與p分別指向不同的記憶體塊了,不會互相影響

4、方法結束,p地址和值沒變(改變的僅僅是p的拷貝p1),但是p所指向的記憶體塊資料被p1所改變了,故*p為11

總結:傳入的指標僅僅是一個拷貝,方法不會改變原指標的地址、值,但是可能會改變原指標所指向記憶體塊的資料。

再補充:

void  main()  
{  
     char  *p=NULL;  
     myMalloc(p);    //這裡的p實際還是NULL,p的值沒有改變,為什麼?  
     if(p)  free(p);  
}  


void  myMalloc(char  *s)  //我想在函式中分配記憶體,再返回  
{  
     s=(char  *)  malloc(100);  
}  

myMalloc(p)的執行過程:  
分配一個臨時變數char  *s,s的值等於p,也就是NULL,但是s佔用的是與p不同的記憶體空間。此後函式的執行與p一點關係都沒有了!只是用p的值來初始化s。  
然後s=(char  *)  malloc(100),把s的值賦成malloc的地址,對p的值沒有任何影響。p的值還是NULL。  
注意指標變數只是一個特殊的變數,實際上它存的是整數值,但是它是記憶體中的某個地址。通過它可以訪問這個地址。  

程式2:void  myMalloc(char  **s)  
{  
     *s=(char  *)  malloc(100);  
}  

void  main()  
{  
     char  *p=NULL;  
     myMalloc(&p);    //這裡的p可以得到正確的值了  
     if(p)  free(p);  
}  
程式2是正確的,為什麼呢?看一個執行過程就知道了:  
myMalloc(&p);將p的地址傳入函式,假設儲存p變數的地址是0x5555,則0x5555這個地址存的是指標變數p的值,也就是Ox5555指向p。  
呼叫的時候同樣分配一個臨時變數char  **s,此時s  的值是&p的值也就是0x5555,但是s所佔的空間是另外的空間,只不過它所指向的值是一個地址:Ox5555。  
*s=(char  *)  malloc(100);這一句話的意思是將s所指向的值,也就是0x5555這個位置上的變數的值賦為(char  *)  malloc(100),而0x5555這個位置上存的是恰好是指標變數p,這樣p的值就變成了(char  *)  malloc(100)的值。即p的值是新分配的這塊記憶體的起始地址。  

這個問題理解起來有點繞,關鍵是理解變數作函式形參呼叫的時候都是要分配一個副本,不管是傳值還是傳址。傳入後就和形參沒有關係了,它不會改變形參的值。myMalloc(p)不會改變p的值,p的值當然是 NULL,它只能改變p所指向的記憶體地址的值。但是myMalloc(&p)為什麼就可以了,它不會改變(&p)的值也不可能改變,但是它可以改變(&p)所指向記憶體地址的值,即p的值