指針引發的問題(以解決)

 由於前幾天寫二叉樹時犯了很嚴重的野指針和分配內存錯誤,害我找錯找了N久,所以發一個這方面的資料,很有用的
A.指針參數是如何傳遞內存的?
如果函數的參數是一個指針,不要指望用該指針去申請動態內存。示例1.1 中,
Test 函數的語句GetMemory(str, 200)並沒有使str 獲得期望的內存,str 依舊是NULL,
爲什麼?
示例1.1:
void GetMemory(char *p, int num)
{
        p = (char *)malloc(sizeof(char) * num);
}
void Test(void)
{
        char *str = NULL;
        GetMemory(str, 100); // str 仍然爲 NULL
        strcpy(str, "hello"); // 運行錯誤
}
毛病出在函數GetMemory 中。編譯器總是要爲函數的每個參數製作臨時副本,指針
參數p 的副本是 _p,編譯器使 _p = p。如果函數體內的程序修改了_p 的內容,就導致
參數p 的內容作相應的修改。這就是指針可以用作輸出參數的原因。在本例中,_p 申請
了新的內存,只是把_p 所指的內存地址改變了,但是p 絲毫未變。所以函數GetMemory
並不能輸出任何東西。事實上,每執行一次GetMemory 就會泄露一塊內存,因爲沒有用
free 釋放內存。
如果非得要用指針參數去申請內存,那麼應該改用“指向指針的指針”,見示例1.2
示例1.2                    
void GetMemory2(char **p, int num)
{
        *p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
        char *str = NULL;
        GetMemory2(&str, 100); // 注意參數是 &str,而不是str
        strcpy(str, "hello");
        cout<< str << endl;
        free(str);
}
由於“指向指針的指針”這個概念不容易理解,我們可以用函數返回值來傳遞動態
內存。這種方法更加簡單,見示例1.3。
示例1.3
char *GetMemory3(int num)
{
        char *p = (char *)malloc(sizeof(char) * num);
        return p;
}
void Test3(void)
{
        char *str = NULL;
        str = GetMemory3(100);
        strcpy(str, "hello");
        cout<< str << endl;
        free(str);
}
用函數返回值來傳遞動態內存這種方法雖然好用,但是常常有人把return 語句用錯
了。這裏強調不要用return 語句返回指向“棧內存”的指針,因爲該內存在函數結束時
自動消亡,見示例1.4.  
示例1.4
char *GetString(void)
{
        char p[] = "hello world";
        return p; // 編譯器將提出警告
}
void Test4(void)
{
        char *str = NULL;
        str = GetString(); // str 的內容是垃圾
        cout<< str << endl;
}
用調試器逐步跟蹤Test4,發現執行str = GetString 語句後str 不再是NULL 指針,
但是str 的內容不是“hello world”而是垃圾。
如果把示例1.4 改寫成示例1.5,會怎麼樣?    
示例1.5
char *GetString2(void)
{
        char *p = "hello world";
        return p;
}
void Test5(void)
{
        char *str = NULL;
        str = GetString2();
        cout<< str << endl;
}
函數Test5 運行雖然不會出錯,但是函數GetString2 的設計概念卻是錯誤的。因
爲GetString2 內的“hello world”是常量字符串,位於靜態存儲區,它在程序生命期
內恆定不變。無論什麼時候調用GetString2,它返回的始終是同一個“只讀”的內存塊。


B.free 和delete 把指針怎麼啦?
別看free 和delete 的名字惡狠狠的(尤其是delete),它們只是把指針所指的內存給
釋放掉,但並沒有把指針本身幹掉。
用調試器跟蹤示例2.1,發現指針p 被free 以後其地址仍然不變(非NULL),只是
該地址對應的內存是垃圾,p 成了“野指針”。如果此時不把p 設置爲NULL,會讓人誤
以爲p 是個合法的指針。
如果程序比較長,我們有時記不住p 所指的內存是否已經被釋放,在繼續使用p 之
前,通常會用語句if (p != NULL)進行防錯處理。很遺憾,此時if 語句起不到防錯作
用,因爲即便p 不是NULL 指針,它也不指向合法的內存塊。
示例2.1
char *p = (char *) malloc(100);
strcpy(p, “hello”);
free(p); // p 所指的內存被釋放,但是p 所指的地址仍然不變

if(p != NULL) // 沒有起到防錯作用
{
strcpy(p, “world”); // 出錯
}
C.動態內存會被自動釋放嗎?
函數體內的局部變量在函數結束時自動消亡。很多人誤以爲示例2.2是正確的。理
由是p 是局部的指針變量,它消亡的時候會讓它所指的動態內存一起完蛋。這是錯覺!
示例2.2
void Func(void)
{
        char *p = (char *) malloc(100); // 動態內存會自動釋放嗎?
}
我們發現指針有一些“似是而非”的特徵:
(1)指針消亡了,並不表示它所指的內存會被自動釋放。
(2)內存被釋放了,並不表示指針會消亡或者成了NULL 指針。
這表明釋放內存並不是一件可以草率對待的事。也許有人不服氣,一定要找出可以
草率行事的理由:
如果程序終止了運行,一切指針都會消亡,動態內存會被操作系統回收。既然如此,                         在程序臨終前,就可以不必釋放內存、不必將指針設置爲NULL 了。終於可以偷懶而不
會發生錯誤了吧?
想得美。如果別人把那段程序取出來用到其它地方怎麼辦?


7.7 杜絕“野指針”
“野指針”不是NULL 指針,是指向“垃圾”內存的指針。人們一般不會錯用NULL
指針,因爲用if 語句很容易判斷。但是“野指針”是很危險的,if 語句對它不起作用。
“野指針”的成因主要有兩種:
(1)指針變量沒有被初始化。任何指針變量剛被創建時不會自動成爲NULL 指針,它
的缺省值是隨機的,它會亂指一氣。所以,指針變量在創建的同時應當被初始化,要麼
將指針設置爲NULL,要麼讓它指向合法的內存。例如
char *p = NULL;
char *str = (char *) malloc(100);
(2)指針p 被free 或者delete 之後,沒有置爲NULL,讓人誤以爲p 是個合法的指針
(3)指針操作超越了變量的作用範圍。這種情況讓人防不勝防,示例程序如下:
class A
{
public:
        void Func(void){ cout << “Func of class A” << endl; }
};
void Test(void)
{
        A *p;
       {
               A a;
               p = &a; // 注意 a 的生命期
        }
       p->Func(); // p 是“野指針”
}
函數Test 在執行語句p->Func()時,對象a 已經消失,而p 是指向a 的,所以p 就
成了“野指針”.


D.一些心得體會                                                                                                                                    

(1)越是怕指針,就越要使用指針。不會正確使用指針,肯定算不上是合格的程序員。
(2)必須養成“使用調試器逐步跟蹤程序”的習慣,只有這樣才能發現問題的本質

發佈了25 篇原創文章 · 獲贊 0 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章