指针到底是什么
指针变量和普通变量的区别
首先必须非常明确:指针的实质就是个变量,它跟普通变量没有任何本质区别。指针完整的名字叫指针变量,简称指针。
为什么需要指针
(1)指针的出现是为了实现间接访问。在汇编中都有间接访问(CPU访问内存通过CPU中的寄存器写入内存地址来实现间接访问,譬如:ldr r0, [r1] 将CPU中的r1寄存器中保存的DDR内存地址处所保存在DDR内存中的值赋值给r0寄存器),其实就是CPU的寻址方式中的间接寻址。
(2)间接访问(CPU的间接寻址)是CPU设计时决定的,这个决定了汇编语言必须能够实现间接寻址,又决定了汇编之上的C语言也必须实现间接访寻址。
(3)高级语言如Java、C#等没有指针,那么他们怎么实现间接访问?答案是语言本身帮我们封装了。
指针使用三部曲:定义指针变量、关联指针变量、解引用
(1)当我们int *p 定义一个指针变量p时,因为p是局部变量,所以也遵循C语言局部变量的一般规律(定义局部变量并且未初始化,则值是随机的),所以此时p变量中存储的是一个随机的数字。
(2)此时如果我们解引用p,则相当于我们访问了这个随机数字为地址的内存空间。那这个空间到底能不能访问不知道(也许可以也许不行),所以如果直接定义指针变量未绑定有效地址就去解引用几乎必死无疑。
(3)定义一个指针变量,不经绑定有效地址就去解引用,就好象拿一个上了镗的枪在四面八方中随意开了一枪。
(4)指针绑定的意义就在于:让指针指向一个可以访问、应该访问的地方(就好象拿着枪瞄准目标的过程一样),指针的解引用是为了间接访问目标变量(就好象开枪是为了打中目标一样)
指针带来的一些符号的理解
我们写的代码是给编译器看的,代码要想达到你想象的结果,就必需要编译器对你的代码的理解和你自己对代码的理解一样。编译器理解代码就是理解的符号,所以我们要正确理解C语言中的符号,才能像编译器一样思考程序、理解代码。
星号*
(1)C语言中*可以表示乘号,也可以表示指针符号。这两个用法是毫无关联的,只是恰好用了同一个符号而已。
(2)在用于指针相关功能的是后续有2种用法:第一种是指针定义时, 结合前面的类型用于表明要定义的指针的类型;第二种功能是指针解引用,解引用时*p表示p指向的变量本身。
取地址符&
取地址符使用时直接加在一个变量的前面,然后取地址符和变量加起来构成一个新的符号,这个符号表示这个变量的地址。
指针定义并未初始化、与指针定义然后赋值的区别
(1)指针定义时可以初始化,指针的初始化其实就是给指针变量初值(跟普通变量的初始化没有任何本质区别)。
(2)指针变量定义同时初始化的格式是:int a = 32; int *p = &a;
(3)不初始化时指针变量先定义再赋值:int a = 32; int *p; p = &a;
不正确 ---- *p = &a;
左值与右值
(1)放在赋值运算符左边的就叫左值,右边的就叫右值。所以赋值操作其实就是:左值 = 右值;
(2)当一个变量做左值时,编译器认为这个变量符号的真实含义是这个变量所对应的那个内存空间;当一个变量做右值时,编译器认为这个变量符号的真实含义是这个变量的值,也就是这个变量所对应的内存空间中存储的那个数。
(3)左值与右值的区别,就好象现实生活中“家”这个字的含义。譬如“我回家了”,这里面的家指的是你家的房子(类似于左值);但是说“家比事业重要”,这时候的家指的是家人(家人就是住在家所对应的那个房子里面的人,类似于右值)
野指针问题
神马是野指针?哪里来的?有什么危害?
我的理解:野指针就是定义了指针没有给指针赋值。
(1)野指针,就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
(2)野指针很可能触发运行时段错误(Sgmentation fault)
(3)因为指针变量在定义时如果未初始化,值也是随机的。指针变量的值其实就是别的变量(指针所指向的那个变量)的地址,所以意味着这个指针指向了一个地址是不确定的变量,这时候去解引用就是去访问这个地址不确定的变量,所以结果是不可知的。
(4)野指针因为指向地址是不可预知的,所以有3种情况:第一种是指向不可访问(操作系统不允许访问的敏感地址,譬如内核空间)的地址,结果是触发段错误,这种情况算好的了,因为编译器会报错;第二种是指向一个可用的、而且没有什么特别意义的空间(譬如我们曾经使用过但是已经不用的栈空间或堆空间),这时候程序运行不会出错,也不会对当前程序造成损害,这种情况下会掩盖你的程序错误,让你以为程序没问题,其实是有问题的;第三种情况就是指向了一个可用的空间,而且这个空间其实在程序中正在被使用(譬如说是程序的一个变量x),那么野指针的解引用就会刚好修改这个变量x的值,导致这个变量莫名其妙的被改变,程序出现离奇的错误。一般最终都会导致程序崩溃,或者数据被损害。这种危害是最大的。
(5)指针变量如果是局部变量,则分配在栈上,本身遵从栈的规律(反复使用,使用完不擦出,所以是脏的,本次在栈上分配到的变量的默认值是上次这个栈空间被使用时余留下来的值),就决定了栈的使用多少会影响这个默认值。因此野指针的值是有一定规律不是完全随机的,但是这个值的规律对我们没意义。因为不管落在上面的野指针是3种情况的哪一种,都不是我们想看到的。
怎么避免野指针?
(1)野指针的错误来源就是指针定义了以后没有初始化,也没有赋值(总之就是指针没有明确的指向一个可用的内存空间),然后去解引用。
(2)知道了野指针产生的原因,避免方法就出来了:在指针的解引用之前,一定确保指针指向一个绝对可用的空间。
(3)常规的做法是:
第一点:定义指针时,同时初始化为NULL
第二点:在指针使用之前,将其赋值绑定给一个可用地址空间
第三点:在指针解引用之前,先去判断这个指针是不是NULL
第四点:指针使用完之后,将其赋值为NULL
(4)野指针的防治方案4点绝对可行,但是略显麻烦。很多人懒得这么做,实战中是怎么处理?在中小型程序中,自己水平可以把握的情况下,不必严格参照这个标准;但是在大型程序中,或者自己水平感觉不好把握时,建议严格参照这个方法。
NULL到底是什么?
(1)NULL在C/C++中定义为:
#ifdefine _cplusplus //定义这个符号就表示当前是C++环境
#define NULL 0 //在C++中NULL就是0
#else
#define NULL (void *)0 //在C中NULL是强制类型转换为void *的0
#endif
(2)在C语言中,int *p,你可以p = (int *)0,但是不可以p = 0,因为类型不同
(3)所以NULL的实质其实就是地址0,然后我们给指针赋初值为NULL,其实就是让指针指向0地址处。为什么指向0地址处?有2点原因:第一层原因是0地址处作为一个特殊地址(我们认为指针指向这里就表示指针没有被初始化,就表示野指针);第二层原因是这个0地址在一般的操作系统中都是不可被访问的,如果不按规矩(不检查是否等于NULL就去解引用)写代码直接去解引用就会触发段错误,编译器会报错嘀。(指针指向这个敏感地址没有问题,只要不解引用就ok)