一、什么是野指针?
指针是C语言的灵魂,同时也是很容易让人犯错的重难点,用错了指针将是一个灾难。
指针变量的本质是值,这个特殊的值是一个内存地址值,而合法的内存地址包括定义的变量的地址(栈)、malloc函数申请堆内存返回的地址(但未使用free释放,是在堆空间动态申请)
需要注意的是,野指针不是NULL指针,通常NULL指针可以使用if语句来判断,但是C语言中没有任何方法用来判断一个指针是否为野指针!
二、野指针是怎么来的?
通常野指针是因为指针变量中保存的值不是一个合法的内存地址或者指向不可用内存的指 针而造成的。
而且野指针往往会造成内存越界、段错误等难以找到的问题,下面分几种情况来说说野指针的由来。
局部指针变量没有被初始化
#include <stdio.h>
#include <string.h>
struct Student
{
char* name;
int number;
};
int main()
{
struct Student s;
strcpy(s.name, "Lixiaoyao"); // OOPS!
s.number = 99;
return 0;
}
局部变量不像全局变量那样,不赋值会自动初始化为0,指针name指向的内存空间地址是随机的,不能向随机地址空间写数据。我们在定义局部指针变量时应该初始化为NULL,局部变量则初始化为0
使用已经释放过后的指针
#include <stdio.h>
#include <malloc.h>
#include <string.h>
void func(char* p)
{
printf("%s\n", p);
free(p);
}
int main()
{
char* s = (char*)malloc(5);
strcpy(s, "Lixiaoyao");//数组越界
func(s);
printf("%s\n", s); // OOPS!使用已经释放的指针s
return 0;
}
malloc申请的堆空间释放后,意味着把这片内存归还到空闲链表,其它程序可以使用这片空间,如果其它程序使用了这个空间,可能导致其它程序莫名其妙的被关闭,所以一定要在释放过后将指针变量的值赋值为NULL。
指针所指向的变量在指针之前被销毁
#include <stdio.h>
char* func()
{
char p[] = "Lixiaoyao";
return p;
}
int main()
{
char* s = func();
printf("%s\n", s); // OOPS!
return 0;
}
func函数被调用的时候,栈区存放了局部数组p,func返回之后,栈顶指针退出,占用的内存已经被释放掉,此时指针s指向一个被释放掉了栈空间,如果栈空间值被修改了,就不会打印出预期结果,s就变成了一个野指针,所以我们绝对不要在函数中返回局部变量和局部数组的地址。
进行了错误指针运算
void main()
{
int a[10] = {1,2,3,4,5,6,7,8,9,10};
int *p;
for (int *p = &a[9];p >= a;){
*--p = 0;
}
}
程序中在数组第1个元素a[0]被清除之后,指针p的值还继续减下去,而接下去的一次比较运算是用于结束循环的。但表达式p>= a(p >= &a[0])的值是未定义的。
为避免这种情况,一定要确保字符数组要以‘\0’结尾,为防止内存越界,自己编写的内存相关函数需要指定正确的长度信息。
进行了错误的强制类型转换
#include <stdio.h>
#include <string.h>
int main()
{
int a = 1;
int p = &a;
printf("%d\n",*((int*)p));
/*
在64位下输出错误
32位下输出a的值 1
*/
return 0;
}
上面的程序在64位下输出错误,32位下输出a的值1,在我们写嵌入式程序的时候,会将int类型的一个数据强制转换成一个指针类型用来表示寄存器的地址,这个时候就需要注意了。
怎么避免野指针?
知道了野指针产生的原因,避免方法就出来了,在指针的解引用之前,确保指针指向一个绝对可用的空间。
- 定义指针时,同时初始化为NULL
- 在指针解引用之前,先去判断这个指针是不是Null
- 指针使用完之后,将其赋值为NULL
- 在指针使用之前,将其赋值绑定给一个可用地址空间