初識野指針
l 野指針通常是因為指針變量中儲存的值不是一個合法的記憶體位址而造成的。
l 野指針不是NULL指針,是指向不可用記憶體的指針。
l NULL指針不容易用錯,因為if語句很好判斷一個指針是不是NULL。
l C語言中沒有任何手段可以判斷一個指針是否為野指針。
野指針的由來
(1)局部指針變量沒有被初始化。
例:
include
include
struct Student
{
char* name;
int number;
};
int main()
{
struct Student s;
strcpy(s.name, "Delphi Tang"); // OOPS!
s.number = 99;
return 0;
}
(2)使用已經釋放過的指針。
例:
include
include
include
void func(char* p)
{
printf("%s\n", p);
free(p);
}
int main()
{
char* s = (char*)malloc(5); //記憶體越界
strcpy(s, "Delphi Tang");
func(s);
printf("%s\n", s); // OOPS!
return 0;
}
使用釋放過的指針,很有可能使一些其他程式被莫名的操作。
(3)指針所指向的變量在指針之前被銷毀。
例:
include
char* func()
{
char p[] = "Delphi Tang"; //儲存在棧區,使用之後就會被釋放
return p;
}
int main()
{
char* s = func(); //s指向一段被釋放了的棧空間。這段空間若是沒有被占用,還是會列印Delphi Tang,若是被占用,列印什麼,就變得未知了。這時候的s就變成了野指針。
printf("%s\n", s); // OOPS!
return 0;
}
二.經典錯誤
非法記憶體操作分析
l 結構體成員指針未初始化
l 沒有為結構體指針配置設定足夠的記憶體(不能越界)
include
include
struct Demo
{
int* p;
};
int main()
{
struct Demo d1;
struct Demo d2;
int i = 0;
for(i=0; i<10; i++)
{
d1.p[i] = 0; // OOPS!
}
//p是未初始化,沒有配置設定位址的指針,也就是野指針,直接給它指派,是錯誤的。
d2.p = (int*)calloc(5, sizeof(int));
for(i=0; i<10; i++)
{
d2.p[i] = i; // OOPS!
}
//配置設定了5個空間,指派10次。若是這段記憶體後面的内容沒人用,那我們用了也沒事。但是,我們若使用了,就會改寫了其他的變量。
free(d2.p);
return 0;
}
記憶體初始化分析
l 記憶體配置設定成功,但是并沒有初始化
include
include
int main()
{
char* s = (char*)malloc(10);
printf(s); // OOPS!
free(s);
return 0;
}
字元串的特點是,以/0結尾,但是我們申請下來的這個字元串是不是以/0結尾确實不一定的。
記憶體越界分析--數組越界
include
void f(int a[10])
{
int i = 0;
for(i=0; i<10; i++)
{
a[i] = i; // OOPS!
printf("%d\n", a[i]);
}
}
//首先這種方式合理,因為數組在傳遞而定過程中,會變為指針。但在指派的時候就出現了錯誤。
int main()
{
int a[5];
f(a);
return 0;
}
記憶體洩漏分析
include
include
void f(unsigned int size)
{
int* p = (int*)malloc(size*sizeof(int));
int i = 0;
if( size % 2 != 0 )
{
return; // OOPS!這裡面的空間沒有釋放,造成了記憶體的損失。
}
for(i=0; i
{
p[i] = i;
printf("%d\n", p[i]);
}
free(p);
}
int main()
{
f(9);
f(10);
return 0;
}
設計函數的時候,最好是單入口,單出口。記憶體洩漏時工程裡很容易忽視的。
為了解決這個問題,我們将上面的函數改成單入口,單出口:
include
include
void f(unsigned int size)
{
int* p = (int*)malloc(size*sizeof(int));
int i = 0;
if( size % 2 == 0 )
{
for(i=0; i
{
p[i] = i;
printf("%d\n", p[i]);
}
}
free(p);
}
int main()
{
f(9);
f(10);
return 0;
}
多次釋放指針
include
include
void f(int* p, int size)
{
int i = 0;
for(i=0; i
{
p[i] = i;
printf("%d\n", p[i]);
}
free(p);
}
int main()
{
int* p = (int*)malloc(5 * sizeof(int));
f(p, 5);
free(p); // OOPS!
return 0;
}
我們這裡做一個習慣的要求---誰申請誰釋放,main函數中申請的動态空間,就在main函數中釋放。多次釋放指針會造成異常退出。
使用已經釋放的指針
include
include
void f(int* p, int size)
{
int i = 0;
for(i=0; i
{
printf("%d\n", p[i]);
}
free(p);
}
int main()
{
int* p = (int*)malloc(5 * sizeof(int));
int i = 0;
f(p, 5);
for(i=0; i<5; i++)
{
p[i] = i; // OOPS!
}
return 0;
}
釋放空間後的指針,就變成了野指針。
三. C語言中的交通規則
用malloc申請了記憶體之後,應該立即檢查指針是否我NULl,防止使用值為NULL的指針
int* p = (int*)malloc(5 * sizeof(int));
if( p != NULL)
{
//do something here
}
free(p);
牢記數組的長度,防止數組越界,考慮使用柔性數組
typedef struct _soft_array
{
int len;
int array[];
}SoftArray;
int i = 0;
SoftArray* sa = (SoftArray)malloc(sizeof(SoftArray)+sizeof(int)10);
sa->len = 10;
for(i=0;ilen;i++)
{
sa->array[i] = i + 1;
}
動态申請的操作必須和釋放的操作相比對,方式記憶體洩漏和多次釋放
void f()
{
int* p = (int*)malloc(10);
free(p);
}
int main()
{
int* p = (int*)malloc(10);
f();
free(p);
return 0;
}
為了解決,我們究竟是在主函數還是子函數中釋放,我們可以在子函數中加第三個參數,來判斷是否需要釋放。
free指針之後必須立即指派為NULL
int* p = (int*)malloc(10);
free(p);
p = NULL;
小結:指針的問題,一般在編譯的過程中是找不出來問題的,隻有在運作的過程中才會把問題顯現出來,是以這裡面的一些規則我們是要牢記的,這樣可以使我們在工程中,少走很多的彎路。