指針作爲函數參數傳遞 (轉載)

指針作爲函數參數傳遞 (轉載)
轉載地址:http://bizhenpo.blog.hexun.com/13559193_d.html

這幾天在學習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語句返回指向“棧內存”的指針,因爲該內存在函數結束時自動消亡]

(注意:實際上沒有混淆,因爲這裏tree是在“堆內”分配的內存,而非在“棧”上。)

所謂一份拷貝,就是在函數調用時,將參數入棧,我們對形參的任何修改都是修改到
棧上的個拷貝,並不影響我們的實際參數.

任何編程語言的參數傳遞實際上都是在做傳值調用.
所謂的傳指針,就是把指針指向者的地址(一個值)傳進函數.
也就是那個地址被壓棧.
然後我們再通過這個地址進行操作,因爲實參和形參同樣都是一個地址的值.
所以改變形參指向者的狀態時,實參指針也能看到這種變化.

這裏區分一下靜態內存,棧內存和動態分配的內存(堆內存)的區別:
(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++編程指南》]

#include<iostream>//指向指針的指針
using namespace std;
void GetMemory(char * &p,int num){
 p=(char *)malloc(sizeof(char)*num);
}
void main(void){
 char *str=NULL;
 GetMemory(str,100);
 strcpy(str,"hello");
 cout<<str<<endl;
 free(str);
}
或者:
#include<iostream>
using namespace std;
void GetMemory(char * *p,int num){
 *p=(char *)malloc(sizeof(char)*num);
}
void main(void){
 char *str=NULL;
 GetMemory(&str,100);
 strcpy(str,"hello");
 cout<<str<<endl;
 free(str);
}
以上都是正確的下面的例子是錯誤的:
#include<iostream>
using namespace std;
void GetMemory(char *p,int num){
 p=(char *)malloc(sizeof(char)*num);
}
void main(void){
 char *str=NULL;
 GetMemory(str,100);
 strcpy(str,"hello");
 cout<<str<<endl;
 free(str);
}

試圖用指針申請動態內存,錯誤的原因上面已經給出了詳細的說明。總而言之,指針作爲參數時,不能在函數體中改變指針的內存地址,要不然,實參的拷貝(壓入棧中)改變了,而實參沒有改變,造成內存泄露並且還達不到預期的效果。上面正確的2個例子都是通過另一種方法繞開了這個問題,改變指針的內容,例如:用了指向指針的指針,給指針的內容改變了,使其變爲新分配內存的首地址,從而達到了效果。
發佈了13 篇原創文章 · 獲贊 9 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章