野指针
神马是野指针?哪里来的?有什么危害?
- (1)野指针,就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
- (2)野指针很可能触发运行时段错误(Sgmentation fault)
- (3)因为指针变量在定义时如果未初始化,值也是随机的。指针变量的值其实就是别的变量(指针所指向的那个变量)的地址,所以意味着这个指针指向了一个地址是不确定的变量,这时候去解引用就是去访问这个地址不确定的变量,所以结果是不可知的。
int *p; // 局部变量,分配在栈上,栈反复被使用,所以值是随机的
//printf("p = %p.\n",p);
*p = 4; // Segmentation fault (core dumped)运行时段错误,原因为野指针
- 野指针因为指向地址是不可预知的,所以有3种情况:
- 第一种是指向不可访问(操作系统不允许访问的敏感地址,譬如内核空间)的地址,结果是触发段错误,这种算是最好的情况了;
- 第二种是指向一个可用的、而且没什么特别意义的空间(譬如我们曾经使用过但是已经不用的栈空间或堆空间),这时候程序运行不会出错,也不会对当前程序造成损害,这种情况下会掩盖你的程序错误,让你以为程序没问题,其实是有问题的;
- 第三种情况就是指向了一个可用的空间,而且这个空间其实在程序中正在被使用(譬如说是程序的一个变量x),那么野指针的解引用就会刚好修改这个变量x的值,导致这个变量莫名其妙的被改变,程序出现离奇的错误。一般最终都会导致程序崩溃,或者数据被损害。这种危害是最大的。
- 指针变量如果是局部变量,则分配在栈上,本身遵从栈的规律(反复使用,使用完不擦除,所以是脏的,本次在栈上分配到的变量的默认值是上次这个栈空间被使用时余留下来的值),就决定了栈的使用多少会影响这个默认值。因此野指针的值是有一定规律不是完全随机,但是这个值的规律对我们没意义。因为不管落在上面野指针3种情况的哪一种,都不是我们想看到的。
怎么避免野指针?
- (1)野指针的错误来源就是指针定义了以后没有初始化,也没有赋值(总之就是指针没有明确的指向一个可用的内存空间),然后去解引用。
- (2)知道了野指针产生的原因,避免方法就出来了:在指针的解引用之前,一定确保指针指向一个绝对可用的空间。
- (3)常规的做法是:
- 第一点:定义指针时,同时初始化为NULL
- 第二点:在指针解引用之前,先去判断这个指针是不是NULL
- 第三点:指针使用完之后,将其赋值为NULL
- 第四点:在指针使用之前,将其赋值绑定给一个可用地址空间
- (4)野指针的防治方案4点绝对可行,但是略显麻烦。很多人懒得这么做,那实践中怎么处理?在中小型程序中,自己水平可以把握的情况下,不必严格参照这个标准;但是在大型程序,或者自己水平感觉不好把握时,建议严格参照这个方法。
int a; int *p = NULL; // 中间省略400行代码······ //p = (int *)4; // 4地址不是你确定可以访问的,就不要用指针去解引用 p = &a; // 正确的使用指针的方式,是解引用指针前跟一个绝对可用的地址绑定 //if (p != NULL) if (NULL != p) { *p = 4; } p = NULL; // 使用完指针变量后,记得将其重新赋值为NULL
NULL到底是什么?
- NULL在C/C++中定义为:
#ifdef _cplusplus // 定义这个符号就表示当前是C++环境 #define NULL 0 // 在C++中NULL就是0 #else #define NULL (void *)0 // 在C中NULL是强制类型转换为void *的0 #endif
- 在C语言中,int *p;你可以p = (int *)0;但是不可以p = 0;因为类型不相同。
- 所以NULL的实质其实就是0,然后我们给指针赋初值为NULL,其实就是让指针指向0地址处。为什么指向0地址处?2个原因:
- 第一层原因是0地址处作为一个特殊地址(我们认为指针指向这里就表示指针没有被初始化,就表示是野指针);
- 第二层原因是这个地址0地址在一般的操作系统中都是不可被访问的,如果C语言程序员不按规矩(不检查是否等于NULL就去解引用)写代码直接去解引用就会触发段错误,这种已经是最好的结果了。
- 一般在判断指针是否野指针时,都写成
- if (NULL != p)而不是写成 if (p != NULL)
- 原因是:如果NULL写在后面,当中间是==号的时候,有时候容易忘记写成了=,这时候其实程序已经错误,但是编译器不会报错。这个错误(对新手)很难检查出来;如果习惯了把NULL写在前面,当错误的把==写成了=时,编译器会报错,程序员会发现这个错误。