21.1.野指针的由来
(1)野指针,就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。野指针很可能会触发运行时段错误(Sgmentation fault)。
(2)因为指针变量在定义时如果未初始化,值也是随机的。指针变量的值其实就是别的变量(指针所指向的那个变量)的地址,则该指针指向了某个地址不确定的变量,此时去解引用就是去访问不确定地址的的变量,所以结果是不可知的。
21.2.野指针的三种情况
(1)第1种情况是指向不可访问的内存地址空间(操作系统不允许访问的敏感地址,譬如内核空间)。野指针解引用的结果是触发段错误,该种情况是最好的情况,可及时处理错误。
(2)第2种情况是指向可用的并且没有特别意义的内存地址空间(譬如我们曾经使用过但是已经不用的栈空间或堆空间)。这种情况下野指针解引用时程序运行不会出错,也不会对当前程序造成损害,但该情况会掩盖程序错误,让程序员误认为程序没有bug。
(3)第3种情况是指向了一个可用的内存地址空间,并且该内存地址空间正在被使用(譬如说是程序的某个变量x),那么野指针解引用就会刚好修改该变量x的值,导致该变量莫名的被改变,程序出现离奇的错误,一般最终都会导致程序崩溃或者数据被损害,这种情况是危害是最大的。
(4)指针变量如果是局部变量,则分配在栈上,本身遵从栈的规律(反复使用,使用完不擦除,所以是脏的,本次在栈上分配到的变量的默认值是上次这个栈空间被使用时余留下来的值),就决定了栈的使用多少会影响这个默认值。因此野指针的值是有一定规律不是完全随机,但是这个值的规律对我们没意义。因为不管落在上面野指针3种情况的哪一种,都不是我们想看到的。
21.3.怎么避免野指针
(1)野指针的错误来源就是指针定义了以后没有初始化,也没有赋值(即指针没有明确的指向合法的内存地址空间),然后取解引用。为避免野指针,则我们必须在指针的解引用之前一定要确保指针指向一个合法的空间。
(2)第1点:定义指针时,同时初始化为NULL;第2点:在指针解引用之前,先去判断这个指针是不是NULL;第3点:指针使用完之后,将其赋值为NULL;第4点:在指针使用之前,将其赋值绑定给一个可用地址空间。
(3)在实践中怎么处理?在中小型程序中,自己水平可以把握的情况下,不必严格参照这个标准;但是在大型程序,或者自己水平感觉不好把握时,建议严格参照这个方法。
21.4.NULL到底是啥
(1)NULL在C/C++中定义为:
(2)所以NULL的实质其实就是0,然后我们给指针赋初值为NULL,其实就是让指针指向0地址处。第1层原因是0地址处作为一个特殊地址(我们认为指针指向这里就表示指针没有被初始化,就表示是野指针);第2层原因是这个地址0地址在一般的操作系统中都是不可被访问的,如果C语言程序员不按规矩(不检查是否等于NULL就去解引用)写代码直接去解引用就会触发段错误,这种已经是最好的结果了。
21.5.const修饰变量的形式
(1)const关键字,在C语言中用来修饰变量,表示这个变量是只读的。const修饰指针有4种形式,区分清楚这4种即可全部理解const和指针:
(2)关于指针变量的理解,主要涉及到2个变量:第1个是指针变量p本身,第2个是p指向的那个变量(*p)。一个const关键字只能修饰一个变量,所以弄清楚这4个表达式的关键就是搞清楚const放在某个位置是修饰谁的。
21.6.const修饰的变量真的不能更改吗
(1)const修饰的变量其实是可以改的(前提是gcc环境下)。在某些单片机环境下,const修饰的变量是不可以改的。const修饰的变量到底能不能真的被修改,取决于具体的环境,C语言本身并没有完全严格一致的要求。
(2)在gcc中,const是通过编译器在编译的时候执行检查来确保实现的(也就是说const类型的变量不能改是编译错误,不是运行时错误)所以我们只要想办法骗过编译器,就可以修改const定义的常量,而运行时不会报错。
(3)更深入一层的原因,是因为gcc把const类型的常量也放在了data段,其实和普通的全局变量放在data段是一样实现的,只是通过编译器认定这个变量是const的,运行时并没有标记const标志,所以只要骗过编译器就可以修改了。
(4)const是在编译器中实现的,编译时检查,并非不能骗过。所以在C语言中使用const,就好象是一种道德约束而非法律约束,所以大家使用const时更多是传递一种信息,就是告诉编译器、也告诉读程序的人,这个变量是不应该也不必被修改的。
21.wild_pointer
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:野指针和const关键字
* 功能:演示野指针。
*/
#include <stdio.h>
int main(int argc, char **argv)
{
int *p; // 局部变量,分配在栈上,栈反复被使用,所以值是随机的
printf("p = %p. &p = %p.\n", p, &p); // p = 0xb7730ff4. &p = 0xbfb65c5c.
//*p = 4; // Segmentation fault (core dumped)运行时段错误,原因为野指针
int *pointer = NULL, a = 10; // 定义指针时同时初始化为NULL
pointer = &a; // 在指针使用前,将其赋值绑定给一个可用地址
if (NULL != pointer) // 在指针解引用前,先判断该指针是否为NULL
{
*pointer = 8;
}
pointer = NULL; // 指针使用完后,将其赋值为NULL
return 0;
}
21.const_pointer
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:野指针和const关键字
* 功能:演示const关键字和指针结合的4种形式。
*/
#include <stdio.h>
int main(int argc, char **argv)
{
#if 0
int const *p1; // p1为指针变量指向可变,P1指向的变量只读
const int *p2; // p2为指针变量指向可变,P2指向的变量只读
int * const p3; // P3为指针变量只读,P3指向的变量的值可变
const int * const p4; // p4为指针变量只读,P4指向的变量只读
int a = 5;
*p1 = 10; // error: assignment of read-only location ‘*p1’
p1 = &a; // 编译无错误无警告
*p2 = 10; // error: assignment of read-only location ‘*p2’
p2 = &a; // 编译无错误无警告
*p3 = 10; // 编译无错误无警告
p3 = &a; // assignment of read-only variable ‘p3’
*p4 = 10; // assignment of read-only location ‘*p4’
p4 = &a; // assignment of read-only variable ‘p4’
#endif
const int a = 8;
//a = 6; // error: assignment of read-only variable ‘a’
int *p = (int *)&a; // 编译无错误无警告
*p = 100; // a = 100.
printf("a = %d.\n", a);
return 0;
}