C之內存操作經典問題解析(三十七)

   在 C 語言中,野指針是一個常見的內存錯誤。那麼野指針到底是什麼呢?指針變量中的值是非法的內存地址,進而形成野指針;野指針不是 NULL 桌子,是指向不可用內存地址的指針;NULL 指針並無危害,很好判斷也很調試;在 C 語言中無法判斷一個指針所保存的地址是否合法,所以我們必須得杜絕野指針!

   那麼野指針到底是怎麼來的呢?來源有這麼幾方面:1、局部指針變量沒有被初始化;2、指針所指向的變量在指針之前被銷燬;3、使用已經釋放過的指針;4、進行了錯誤的指針運算;5、進行了錯誤的強制類型轉換。

   下來我們以代碼爲例進行分析,代碼如下

#include <stdio.h>
#include <malloc.h>
 
int main()
{
   int* p1 = (int*)malloc(40);
   int* p2 = (int*)1234567;
   int i = 0;
     
   for(i=0; i<40; i++)
   {
     *(p1 + i) = 40 - i;
   }
 
   free(p1); 
     
   for(i=0; i<40; i++)
   {
     p1[i] = p2[i];
   }
     
   return 0;
}

   我們看到第 7 行進行了錯誤的強制類型轉換,在第 12 行進行指針的賦值,但是由於申請的只有40字字節的大小,但是我們進行40個 int 類型的數賦值,這個進越界啦。我們在第 15 行釋放了指針 p1,但是在第 19 行又使用了指針 p1,這也會出錯。我們看看編譯後的結果

圖片.png 

   我們看到直接報段錯誤了,這便是操作了野指針帶來的影響。我們在 C 語言中有這麼幾個基本原則:a> 絕不返回局部變量和局部數組的地址;b> 任何變量在定義後必須 0 初始化;c> 字符數組必須確認 0 結束符後才能成爲字符串;d> 任何使用與內存相關的函數必須指定長度信息。

   我們再看一份代碼,在這份代碼裏野指針更是無處不在

#include <stdio.h> 
#include <string.h>
#include <malloc.h>
 
struct Student
{
   char* name;
   int number;
};
 
char* func()
{
   char p[] = "D.T.Software";
     
   return p;
}
 
void del(char* p)
{
   printf("%s\n", p);
     
   free(p);
}
 
int main()
{
   struct Student s;
   char* p = func();
     
    strcpy(s.name, p); 
     
   s.number = 99;
     
   p = (char*)malloc(5);
   
   strcpy(p, "D.T.Software");
     
   del(p);
     
   return 0;
}

      我們首先在第27行定義了一個結構體變量 s,但是結構體裏的指針類型的成員變量沒進行初始化。接着定義指針 p 並調用 func 函數進行初始化,但是在 func 函數中返回了局部數組的地址。在第34行申請了5個char*類型大小的空間,在第36行進行字符串的複製,但是越界了。我們看到在這份代碼中,野指針是無處不在的。

  內存錯誤是實際產品開發中最常見的問題,然而絕大多數 bug 都可以通過遵循基本的編程原則和規範來避免。因此,我們在學習的時候要牢記和理解內存操作的基本原則,目的和意義,這樣才能達到最小程度的減少 bug。

  常見的內存錯誤有:a> 結構體成員指針未初始化;b> 結構體成員指針未分配足夠的內存;c> 內存分配成功,但並未初始化;d> 內存操作越界。

  我們再以代碼爲例進行分析說明

#include <stdio.h>
#include <malloc.h>
 
void test(int* p, int size)
{
   int i = 0;
     
   for(i=0; i<size; i++)
   {
       printf("%d\n", p[i]);
   }
     
   free(p);
 }
 
void func(unsigned int size)
{
   int* p = (int*)malloc(size * sizeof(int));
   int i = 0;
     
   if( size % 2 != 0 )
   {
      return; 
   }
     
   for(i=0; i<size; i++)
   {
      p[i] = i;
      printf("%d\n", p[i]);
   }
     
   free(p);
 }
 
int main()
 {
   int* p = (int*)malloc(5 * sizeof(int));
     
   test(p, 5);
     
   free(p); 
     
   func(9);
   func(10);
        
   return 0;
}

      我們看到在程序的第37行申請了堆空間 ,用指針 p 指向這片堆空間。調用了 test 函數,但是在 test 函數內部進行了指針的釋放。這是錯誤的 ,free 是用來釋放堆上申請的空間的,但在這塊我們釋放的是棧上的內存,會造成段錯誤。我們在第41行繼續釋放 p,造成了指針的重複釋放。我們在 func 函數中如果傳入的是奇數便直接返回,申請的內存空間也沒有釋放,這樣會造成內存泄漏。

  編譯結果如下

圖片.png

        再來看一個程序

#include <stdio.h>
#include <malloc.h>
 
struct Demo
{
    char* p;
};
 
int main()
{
    struct Demo d1;
    struct Demo d2;
     
    char i = 0;
     
    for(i='a'; i<'z'; i++)
    {
        d1.p[i] = 0; 
    }
     
    d2.p = (char*)calloc(5, sizeof(char));
     
    printf("%s\n", d2.p);
     
    for(i='a'; i<'z'; i++)
    {
        d2.p[i] = i; 
    }
    
    free(d2.p);
     
    return 0;
}

       我們看到定義的兩個結構體,他們的指針成員變量沒有進行初始化,在第21行申請堆空間,在第27行進行賦值,但是內存越界了。我們看看編譯結果

圖片.png    

   同樣直接報段錯誤。那麼我們在動態內存申請之後,應立即檢查指針值是否爲 NULL,防止使用 NULL 指針。free 指針之後必須立即賦值爲 NULL!任何與內存操作相關的函數都必須帶長度信息。malloc 操作和 free 操作必須匹配,防止內存泄漏和多次釋放。

   內存錯誤的本質來源於指針保存的地址爲非法值,指針變量爲初始化,保存隨機值。指針運算導致內存越界。內存泄漏源於 malloc 和 free 不匹配,當 malloc 次數多於 free時,產生內存泄漏;當 malloc 次數少於 free 時,程序可能崩潰!


   歡迎大家一起來學習 C 語言,可以加我QQ:243343083

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