有關指針的心得之指針常見錯誤

轉自:http://www.eefocus.com/zhangjingbin/blog/10-05/190629_014f8.html

1.常見的內存錯誤及對策

      發生內存錯誤是件非常麻煩 的事情。編譯器不能自動發現這些錯誤,通常是在程序運行時才能捕捉到。而這些錯誤大多沒有明顯的症狀,時隱時現,增加了改錯的難度。有時用戶怒氣衝衝地把 你找來,程序卻沒有發生任何問題,你一走,錯誤又發作了。 常見的內存錯誤及其對策如下:
    (1) 內存分配未成功,卻使用了它。
     編程新手常犯這種錯誤,因爲他們沒有意識到內存分配會不成功。常用解決辦法是,在使用內存之前檢查指針是否爲NULL。如果指針p是函數的參數,那麼在 函數的入口處用assert(p!=NULL)進行檢查。如果是用malloc或new來申請內存,應該用if(p==NULL) 或if(p!=NULL)進行防錯處理。
    (2)內存分配雖然成功,但是尚未初始化就引用它。
      犯這種錯誤主要有兩個 起因:一是沒有初始化的觀念;二是誤以爲內存的缺省初值全爲零,導致引用初值錯誤(例如數組)。內存的缺省初值究竟是什麼並沒有統一的標準,儘管有些時候 爲零值,我們寧可信其無不可信其有。所以無論用何種方式創建數組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。
    (3)內存分配成功並且已經初始化,但操作越過了內存的邊界。
      例如在使用數組時經常發生下標“多1”或者“少1”的操作。特別是在for循 環語句中,循環次數很容易搞錯,導致數組操作越界。
    (4)忘記了釋放內存,造成內存泄露。
     含有這種錯誤的函數每被 調用一次就丟失一塊內存。剛開始時系統的內存充足,你看不到錯誤。終有一次程序突然死掉,系統出現提示:內存耗盡。動態內存的申請與 釋放必須配對,程序中malloc與free的使用次數一定要相同,否則肯定有錯誤(new/delete同理)。

    (5)釋放了內存卻繼續使用它(所謂的野指針)。一般情況下,釋放掉指針後將指針置爲空。


2.指針與數組對比

      C++/C程序中,指針和數組在不少地方可以相互替換着用,讓人產生一種錯覺,以爲兩者是等價的。數組要麼在靜態存儲區被創建(如全局數組),要麼在棧上被創建。數組名對應着(而不是指向)一塊內存,其地址與容量在生命期內保持不變,只有數組的內容 可以改變。指針可以隨時指向任意類型的內存塊,它的特徵是“可變”,所以我們常用指針來操作動態內存。指針遠比數組靈活,但也更危險。


例子1:

     字符數組a的 容量是6個字符,其內容爲hello。a的內容可以改變,如a[0]= 'X’。指針p指向常量字符串“world”(位於靜態存儲區,內容爲world),常量字符串的內容是不可以被修改的。從語法上看,編譯器並不覺得語句 p[0]= 'X’有什麼不妥,但是該語句企圖修改常量字符串的內容而導致運行錯誤。

  1. char a[] = “hello”;  
  2. a[0] = 'X’;  
  3. cout << a << endl;  
  4. char *p = “world”; // 注意p指向常量字符串  
  5. p[0] = 'X’; // 編譯器不能發現該錯誤  
  6. cout << p << endl;  <span style="font-size: 14px; line-height: 22.383333206176758px; color: rgb(51, 51, 51); font-family: Verdana, Arial, Helvetica, sans-serif, 宋體; ">    </span>  

例子2:

用運算符sizeof可以計算出數組的容量(字節數)。注意當數組作爲函數的參數進行傳遞時,該數組自動退化爲 同類型的指針。不論數組a的容量是多少,sizeof(a)始終等於指針的容量

  1. char a[] = "hello world";  
  2. char *p = a;  
  3. cout<< sizeof(a) << endl; // 12字節(注意別忘了加上末尾的‘\0’)  
  4. cout<< sizeof(p) << endl; // 4字節     示例3.3(a) 計算數組和指針的內存容量  
  5.   
  6. void Func(char a[100])  
  7. {  
  8.      cout<< sizeof(a) << endl; // 4字節而不是100字節  
  9. }  


3.指針參數傳遞內存

    用函數返回值來傳遞動態內存這種方法雖然好用,但是常常有人把return語句用錯了。(下面的用法是對的)

  1. char *GetMemory3(int num)  
  2. {  
  3.     char *p = (char *)malloc(sizeof(char) * num);  
  4.     return p;  
  5. }  
  6. void Test3(void)  
  7. {  
  8.     char *str = NULL;  
  9.     str = GetMemory3(100);  
  10.     strcpy(str, "hello");  
  11.     cout<< str << endl;  
  12.     free(str);  
  13. }  <span style="color: rgb(51, 51, 51); font-family: Verdana, Arial, Helvetica, sans-serif, 宋體; font-size: 14px; line-height: 22.383333206176758px; "> </span>  

      這裏強調不要用 return語句返回指向“棧內存”的指針,因爲該內存在函數結束時自動消亡。(下面就是錯的)

  1. char *GetString(void)  
  2. {  
  3.     char p[] = "hello world";  
  4.     return p; // 編譯器將提出警告  
  5. }  
  6. void Test4(void)  
  7. {  
  8.     char *str = NULL;  
  9.     str = GetString(); // str 的內容是垃圾  
  10.     cout<< str << endl;  
  11. }     

用調試器逐步跟蹤Test4,發現執行str = GetString語句後str不再是NULL指針,但是str的內容不是“hello world”而是垃圾。
如果把示例改成下面這樣

  1. char *GetString2(void)  
  2. {  
  3.     char *p = "hello world";  
  4.     return p;  
  5. }  
  6. void Test5(void)  
  7. {  
  8.     char *str = NULL;  
  9.     str = GetString2();  
  10.     cout<< str << endl;  
  11. }   
    函數Test5運行雖然不會出錯,但是函數GetString2的設計概念卻是錯 誤的。因爲GetString2內的“hello world”是常量字符串,位於靜態存儲區,它在程序生命期內恆定不變。無論什麼時候調用GetString2,它返回的始終是同一個“只讀”的內存塊。


4.杜絕野指針

     野指針”不是NULL指針,是指向“垃圾”內存的指針。人們一般不會錯用NULL指針,因爲用if語句很容易 判斷。但是“野指針”是很危險的,if語句對它不起作用。 “野指針”的成因主要有兩種:

     (1)指針變量沒有被初始化。任何指針變量 剛被創建時不會自動成爲NULL指針,它的缺省值是隨機的,它會亂指一氣。所以,指針變量在創建的同時應當被初始化,要麼將指針設置爲NULL,要麼讓它 指向合法的內存。例如

               char *p = NULL;
               char *str = (char *) malloc(100);

               //下面是非法的

               char *str;

                scanf("%s",str);//str都沒有賦初值,指向一個未知的地址空間,這是不行的

               //但下面這樣卻是可以的

               char str[10];//編譯時爲它分配了內存空間,它有確定的地址

                scanf("%s",str);

               //同樣下面這樣也是可以的

               char str[10],*a;

               a=str;

               scanf("%s",a);
     (2)指針p被free或者delete之後,沒有置爲NULL,讓人誤以爲p是個合法的指針。

     (3)指針操作超越了變量的作用範 圍。這種情況讓人防不勝防,示例程序如下:

  1. class A  
  2. {  
  3. public:  
  4.     void Func(void){ cout << “Func of class A” << endl; }  
  5. };  
  6. void Test(void)  
  7. {  
  8.     A *p;  
  9.    {  
  10.     A a;  
  11.     p = &a; // 注意 a 的生命期  
  12.     }  
  13.     p->Func(); // p是“野指針”  
  14. }  

        函數Test在執行語句p->Func()時,對象a已經消失,而p是指向a的,所以p就成了“野指針”。但奇怪的是我運行這個 程序時居然沒有出錯,這可能與編譯器有關。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章