一、指针的步长
1.为什么要定义指针的步长这一概念?
我们知道,同一编译器下的内存大小都是一定的,而所谓地址也就是内存的编号,也就是说地址的范围也是固定的。(地址=指针=内存的编号)。既然指针变量存放的都是地址,也就是说,不管什么类型的指针变量,它的大小都是相同的。换个角度说,不管什么类型的指针,大小只与系统内存有关,只与编译器有关。
那么,在32位编译器中,指针变量的大小为4字节;而在64位编译器中,指针变量的大小则为8字节。
那么我们就会疑惑了。既然指针变量的大小都是一样的,那为什么我们还要定义不同类型的指针变量?
这是因为,虽然指针变量存放的都是地址,指针变量的大小都相同,但是不同类型的指针变量,取指针指向的内容的宽度是与类型有关系的。如果是char*类型的指针,在取内容时他只能取出一个字节的内容,而如果是int*类型的指针,在取内容时他只能取出4个字节的内容。
我们发现,不同指针类型的指针宽度是不同的。于是乎,就有了步长的概念。
2.什么是指针的步长?
所谓步长,就是不同类型的指针变量,取指针指向的内容宽度。
在代码中,步长就是指针变量+1所跨过的字节大小。
步长的大小往往等于该指针变量所存储的类型的地址对应的那个类型的大小。
二、野指针
1.什么是野指针?
野指针,就是没有初始化的指针。野指针的指向是随机的、不可操作的。
2.为什么说野指针的指向是随机的,不可操作的?
首先,如果我们在栈区申请了一个局部变量指针,由于局部变量未初始化的值是随机的,那么指针在定义后就会随机保存一个地址,而这个地址可能指向内存中任何一个可能的地方,包括代码区、文字常量区。 而为了避免这样的情况发生,操作系统不允许操作一个指向区域是未知的指针所指向的内存空间,说的有点绕,就是操作系统不允许操作野指针指向的内存区域,因为它是未知的,不可操作的。
而如果在静态全局区申请了一个指针,如果没有初始化,则系统默认执行0x0000 0000,也就是NULL,这个地址存放的也是代码区,而代码区是用来加载可执行代码的。在运行期间这块内存不能修改。也就是说野指针的指向还是不可操作的。
总结一下,也就是说:
我们使用指针的原则:指针保存的地址一定是定义过的。
3.如何避免野指针?
使用野指针,也就是操作一个未初始化的指针所指向的空间怕是我们最容易犯的一个错误。
如果我们保证使用指针的原则,指针保存的地址一定是定义过的,就可以避免野指针的存在。我们要避免野指针其实就是要避免指针乱指,指向了不可操作的内存区,比如文字常量区和代码区等。
我们一般避免的方式有两种:
(1).在定义后就让该指针指向一个已定义过的变量所处的内存空间
(2).在堆区申请一块内存,让该指针直接指向这个在堆区的地址。因为堆区就是提供给用户动态申请和释放内存的大容器,是可操作的。
4.定义一个野指针,在定义后系统编译是否会报错呢?
答案是不会。前面我们提到过,操作系统不允许操作一个指向区域是未知的指针所指向的内存空间,也就是说操作系统不允许操作一个野指针。但是,野指针不会直接引发错误,因为野指针就是一个指针变量,是变量就可以像整型等其他变量一样可以任意赋值,只需保证不越界即可。
这里我们总结一下:
野指针不会直接引发错误,操作野指针指向的内存空间才会出问题。
5.野指针的典型案例
int a = 100;
int *p;
p= a; //将a的值赋值给指针变量p,p为野指针,不会有问题,但没有意义
p= 0x12345678;//给指针变量p赋值,p为野指针,不会有问题,但没有意义
*p = 1000;//野指针指向未知区域,内存出问题,err
三、空指针(NULL指针)
1.什么是空指针?
我们前面提到过,NULL也就0x0000 0000,空指针就是指向NULL这个地址的指针。
例:int *p =NULL;
2.空指针的作用是什么?
我们前面提到过0x0000 0000是处在代码区的,那么这个空指针是不可操作的。那为什么我们要专门让指向NULL这个空间的指针定义为空指针呢?
这里我们可以结合NULL这个地址在运行过程中是不允许被使用的特性来思考:如果我们操作空指针,就是非法操作。而很多时候,比如完成一个初始化的代码块时,我们希望某些指针只被使用一次,那我们在使用时只需要判断指针是否为NULL就可以知道这个指针是否被使用过。
除此之外,空指针还可以有什么作用呢?空指针也可以标志为没有任何指向任何变量的指针,也就是说这个空指针没有指向任何变量,也就说,这个指针是空闲可用的。
总结下,程序员使用这个地址的主要作用是:
1.如果使用完指针后,便将指针赋值为NULL,在使用时只需要判断指针是否为NULL就可以知道是否被使用。
2.标志着这个指针没有指向任何变量,是空闲可用的。
3.为什么在使用完指针后,不立即释放指针并将其指向空指针,而是保留指针变量并将其赋值为空指针?
在使用指针时,有时候我们希望在稍后的代码中继续使用同一个指针变量,但此时并不希望它指向任何有效的内存地址。这种情况下,将指针赋值为空指针可以起到以下几个作用:
- 防止悬挂指针:如果在使用完指针后立即释放指针并且不将其指向空指针,那么如果后续代码意外地访问了该指针,就会产生悬挂指针的问题。将指针赋值为空指针可以避免这种悬挂指针的出现。
- 标记指针状态:通过将指针赋值为空指针,可以将指针标记为“无效”状态,表示此时它不应该被使用。这有助于提醒程序员,在之后的代码中需要重新赋值给指针之前,不应该使用该指针。
- 方便错误检查:将指针赋值为空指针后,可以在需要使用指针之前添加条件检查,以确保指针不为空。这样可以帮助捕获潜在的错误或异常情况。
总之,将指针赋值为空指针是一种良好的编程实践,可以帮助避免悬挂指针和提供更好的代码可读性和健壮性。
四、万能指针
1.什么是万能指针?
所谓万能指针,就是可以保存任意类型变量的地址。我们编写代码时,就将(void *)类型的指针称为万能指针。
例:(void *) p=NULL;
2.为什么要使用万能指针?
我们前面提到了指针步长的概念,那么我们知道如int、float、char等基本类型的变量的地址只能存放在int*、float* 、char*类型的指针变量中,我们在操作时才能正常引用。
可是如果我们需要保存很多不同类型的或者不确定类型变量的地址怎么办?如果保存很多不同已知类型的变量我们尚且还可以分别定义对应不同类型变量的不同类型的指针变量。那如果需要让它保存任意类型的未知变量的地址怎么办?
我们前面提过,指针变量的大小都是一定的,那么,(void *)类型的指针变量就和其他基本类型的指针变量一样,大小是固定的,可以存放任意一个地址。而系统是也可以顺利申请已知内存大小的变量。
我们前面还提到过,不同类型的指针变量的步长是不一样的。但是void并不是一个基本类型,系统根本不知道void这个类型所占的空间大小,也就是说,(void *)类型的指针变量根本就没有步长。那么,如果我们需要取内容怎么办?很简单,在取内容的时候再强制转换一下就可以了。
这里我们总结一下:
我们可以用万能指针保存任意类型变量的地址。
但如果我们要取内容,需要强制转换为对应类型的指针。
3.使用万能指针的例子
int a =10;
void * p =(void *)&a;
printf("%d\n",*(int *)p);