> 文档中心 > 这...杀穿指针?究极详解

这...杀穿指针?究极详解

文章目录

  • 一、指针含义
  • 二、指针和指针类型
    • 2.1指针类型意义
  • 三、野指针
    • 3.1 野指针成因
    • 3.2所以要及时止损,远离野指针
  • 四、指针运算
  • 五、指针和数组
    • 5.1数组名与指针的关系
    • 5.2指针数组
  • 六、二级指针

一、指针含义

开开开开始今天的内容!首先上定义,指针就是内存中最小单元的编号—也就是地址。而我们平常所说的指针一般指的是指针变量(即存储指针的变量)。

在这里插入图片描述
可以看到一个指针对应一个字节的地址,并且图中地址编号都是0x…的格式,这其实是16进制的格式,当地址打印在屏幕上时也是以十六进制的格式打印的。

那么一个指针大小是多少呢?

这里我们就需要简单普及一下指针的基本原理。

在32位的操作系统中,有32条地址线/数据线(物理的电线)通电后会产生高电平和低电平这两种电信号(转换成数字信号(1/0),当32条电线同时通电时会产生32种电信号,排列组合可知能产生2^32种不同的电信号(同理数字信号))。于是就用这个编号来做地址。而一个地址有32个比特位换算成字节就是4。
在64位的操作系统中同理。
所以可知一个指针大小可以是4个字节也可以是8个字节,具体根据操作系统情况而定。

那么如何去获取一个变量的内存地址呢?

我们需要用到’ &'这个取地址操作符

指针变量

而当你把取出来的地址放到一个变量中存储起来的时候,这个用来存储地址的变量就被称为
如:

#includeint main(){int a=0;   //这里我们对变量a,取出它的地址,可以使用&操作符。int * p = &a;  //a的数据类型为int ,大小为4个字节,这里用'&'操作符将a第一个字节的地址存到p中去,p就是一个指针变量。return 0;}

总结:

1.指针是用来存放地址的,地址是唯一标示一块地址空间的。
2.指针的大小在32位平台是4个字节,在64位平台是8个字节。

二、指针和指针类型

我们知道对于变量来说有不同的数据类型之分,那么指针有没有呢?
答案是肯定的。

当存储不同数据类型的变量的内存地址的时候,需要用对应类型的指针变量来进行接收,这也就侧面说明了指针是有类型之分的。

对各种类型的指针变量定义的格式如下:

char  *pc = NULL;int   *pi = NULL;short *ps = NULL;long  *pl = NULL;float *pf = NULL;double *pd = NULL;

这里可以看到,指针的定义方式是: type + * 。
同时

char* 类型的指针是为了存放 char 类型变量的地址。 short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
注意此处的char的意思可以理解为指针所指向的变量的数据类型,而 ’*‘ 则表示这是指针变量。就第一条来说指针变量的类型是char * ,而不是char。
那么我们该如何理解指针类型的意义呢?

2.1指针类型意义

先来看看指针加减时的情况

#includeint main(){int n = 10;char* pc = (char*)&n; //pc是类型为char * 的指针变量int* pi = &n;    //pi是类型为int *的指针变量printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc + 1);printf("%p\n", pi);printf("%p\n", pi + 1);return 0;}

代码输出结果为:

在这里插入图片描述

一起来分析一下这个代码结果
1.前两个数值相等可以证实变量pc中存储的就是变量n的内存地址
2.我们发现类型为char *的指针变量加1后地址编号只加了1而 int * 类型的指针变量加1后地址编号却增加了4. 而结合数据类型来分析,char数据类型大小为1个字节,int数据类型大小为4个字节。

所以不同类型的指针走一步的步长不一样。

接着再来分析一下对指针解引用时的情况

先了解一下解引用操作符’ * ‘,作用是找回操作的指针变量中存储的地址所指向的变量的值。

接着来观察一下这个代码

#includeint main(){  int a=0x11223344;   //一般以十六进制读取内存,所以这里将变量a赋值十六进制的数便于观察内存,看出不同类型指针解引用后变化char* p1 = (char*)&a;//此处a是int*类型的数据,地址是int*类型的,所以存到char*类型的指针变量时要用强制转换符将指针转换为char*类型的*p1 = 0;int* p2 = &a;*p2 = 0;return 0;}

调试打开内存窗口执行完给a赋值的操作,观察a的内存情况。(注意内存窗口的数据读取是从右到左进行的,并且是十六进制的形式,所以内存中的数据是11
22 33 44,并且每两位是一个字节大小)

在这里插入图片描述

再将调试继续走下去执行完 *p1=0 (即用地址找回原本变量并且重新赋值为0)这步操作

在这里插入图片描述

观察此时a中内存情况的变化,我们可以发现对char*类型的指针变量p1进行了解引用操作找回a的值并重新赋值为0后,只有44被重新赋值为了00,而44又是占一个字节的大小。即char * 类型的指针变量所访问的空间大小为1个字节。

调试继续走下去执行完 *p2=0 (即用地址找回原本变量并且重新赋值为0)这步操作

在这里插入图片描述

我们可以看到此时a中的所有内容都被重新赋值为了0,此时int * 类型的指针变量所访问的空间大小变为了4个字节。

总结:

不同类型的指针的访问权限大小不一样,解引用时可操作的空间大小不一样

这里再做一点补充:

不知道细心的小伙伴有没有注意到刚刚char *类型的指针变量进行解引用操作并重新复制后改变的时 44 而不是 11,那么这是为什么呢?

我们将内存窗口的列数改变为一行,再来观察一下每个数据的地址的高低情况。
这...杀穿指针?究极详解
(左边那一栏是地址编号)可以看到从 44 到 11 的地址是从低位到高位的。
那么这意味着什么呢(1.数据读取到内存中的数据 2.对变量中数据进行操作的先后顺序)

三、野指针

3.1 野指针成因

1.指针未初始化

#include int main(){  int *a;//局部变量指针未初始化,默认为随机值    *a = 10; return 0;}

2.指针越界访问

#include int main(){    int arr[10] = {0};    int *p = arr;    int i = 0;    for(i=0; i<=11; i++)   {      *(p++) = i;//当指针指向的范围超出数组arr的范围时,p就是野指针   }    return 0; }
  1. 指针指向的空间释放

这一块内容就等俺学到了动态内存的相关内容再来补充赘述吧

3.2所以要及时止损,远离野指针

敲黑板了,这五个点一定要牢记啦!

  1. 指针初始化
  2. 避免指针越界(多检查检查呗)
  3. 指针指向空间释放即时初始化为NULL值(这里注意一旦设置为NULL值就不能再次使用)
  4. 避免返回局部变量的地址(因为局部变量出了作用域就会自动销毁,地址对应的内容就是随机得了。)
  5. 指针使用之前检查有效性

四、指针运算

那就只能是这三个啦

*指针± 整数 (这个在上面举例指针类型作用时提到了哲理九不再啰嗦啦)
*指针-指针
*指针的关系运算

指针减指针

#includeint main(){int arr[10]={0};printf("%d",&arr[0]-&arr[9]);printf("%d",&arr[9]-&arr[0]);return 0;}

我们来看一下这个代码结果
在这里插入图片描述

可以得出结论指针相减结果就是中间的元素个数,而且可以是负的。那么这里我们再奇思妙想一波,基于这一点特点是不是能模拟实现 strlen 函数呢。

但在使用前得注意

两个相减的指针必须指向用一个数组。

五、指针和数组

在数组中使用指针时,注意只允许指向数组元素的指针与指向数组最后一个元素后面那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针比较。

5.1数组名与指针的关系

看下面这串代码

#includeint main(){int arr[10]={0};int *p=arr;      //这里%x的意思是以十六进制的形式显示&arr[0]的值,因为这里地址在内存存储格式也是十六进制的,便于观察。printf("%d\n", &arr[0]);//打印arr数组首位元素地址printf("%x",p);  return 0;}

代码结果
在这里插入图片描述

我们可以看到首元素地址和将数组名传给指针p后,两者的值是一样的。即得出结论数组名表示着数组首元素的地址(除了1.在对数组名用sizeof()操作符时,arr代表整个数组2.对数组名用&取地址操作符时,如果&arr+1,则地址会跳过整个数组)。

5.2指针数组

指针数组顾名思义就是用来存放指针的数组啦。

简单看看下面这个代码就明了了

#includeint main(){int a=0;int b=0;int *arr[10]={0};  //这里创建了指针数组arr[0]=&a;  //将a的地址存放到该数组的第1个元素当中去arr[1]=&b;  //将b的地址存放到该数组的第2个元素当中去return 0;}

六、二级指针

既然变量的地址存放在一级指针中,那么一级指针的地址又是否能存放呢?
当然有,那就是二级指针。

#includeint main(){int a=0;int *p=&a;int **p1=&p;//这里p1存的是一级指针p1的地址,p1在这里即为二级指针}

如图
在这里插入图片描述

当然此时进行解引用操作时
*p找到的是变量a的值,
*p1找到的是一级指针 p 的值,
**p1找到的才是变量a的值。

好啦!!!
这...杀穿指针?究极详解
码完了才恍然大明白现在已经是凌晨了,俺的一头乌黑秀发已经不允许我再这么放纵下去了,小伙伴们加油干!
本人也是c语言小白一枚,如内容有误欢迎评论区大佬留言指正。