指针详解+剖析

简介: 我们可以通过&取地址操作符取出变量的内存地址,然后把这个地址呢就存放在一个变量中,这个变量就是指针变量。

在本文章中我会比较详细的介绍有关指针的知识,这些基本上都是基础知识,欢迎小白学习,同时也欢迎大家指出我的不足,共同进步。


一.指针是什么呢?


 指针是什么?
 指针理解:
 1.指针是内存单元的一个编号,也就是地址
 2.平时叫的指针通常是指针变量,是用来存放地址的变量。


总结:指针就是地址,口语中的指针通常是指针变量。


那么我们就可以这样理解内存


5685791ed2e74af6aa470571a9e2c830.png


指针变量:


我们可以通过&取地址操作符取出变量的内存地址,然后把这个地址呢就存放在一个变量中,这个变量就是指针变量。


#include <stdio.h>
int main()
{
  int a=20;//在内存中开辟一个空间来存放变量a
  &a;//&取地址操作符可以把a的地址给取出来;
  int* p = &a;//a变量占用4个字节的空间,这里将a的4个字节的第一个字节地址存放在
  //pz中,p就是一个指针变量
  return 0;
}


总结:

指针变量,用来存放地址的变量。

那问题来了:


1.一个地址单元到底有多大呢?(1个字节)

2.地址是如何编址的呢?


经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。


对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);


那么32根地址线产生的地址就会是:


b229e47ae0104bea9c6e1ecef70e5396.png


这里就有2的32次方个地址


每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB ==232/1024/1024MB==232/1024/1024/1024GB == 4GB) 4G的空闲进行编址。


同样,在64位机器上,如果给64跟地址线,那么能编址多大空间,可以自己算算。


这下我们可以理解:


  • 在32位的机器上,地址是32个0或1组成的二进制序列,那么地址就得用4个字节的空间来存贮,所以一个指针变量的大小就是4个字节。


  • 那如果在64位机器上,就会有64个地址线,那一个指针变量的大小就是8个字节,才能存放一个地址。


总结


  • 指针是用来存放地址的,地址是唯一表示一块地址空间的


  • 指针的大小与指针类型无关,与机器平台有32位平台,指针大小是4个字节,平台有64为那么大小为8个字节。


二.指针和指针有哪些类型?


指针有哪些类型呢?你知道嘛?


我们都知道,变量就有许多类型,整形,浮点型,长整形,字符类型,那指针有没有类型呢?


答案是肯定的。


int n = 10;
现在我们有一个变量n我们想把变量n的地址取出来该怎么弄呢?
需要用&(取地址操作符)存放在变量p中。
那是不是这样写呢?
p=&n;


答案是错误的,正确的写法应该是 int *p=&n;


指针变量类型:


char *p1=NULL;
int *p=NULL;
short *p=NULL;
long *p=NULL;
float *p=NULL;
double *p=NULL;


这里我们可以看到指针变量的定义是 type+这种形式并且前面的type就决定了它是什么样的指针类型:


char*类型的指针就是为了存放char类型变量的地址
int *类型的指针就是为了存放int类型变量的地址
double *类型的指针就是为了存放double类型变量的地址的
到这里你应该理解一些了吧。
不过问题又来了,指针类型有什么意义?


2.1指针的运算 ±整数


int main()
{
  int n = 20;
  char* ch = &n;
  int* p = &n;
  printf("%p\n", &n);
  printf("%p\n", ch);
  printf("%p\n", ch+1);
  printf("%p\n", p);
  printf("%p\n", p+1);
  return 0;
}


f37159aafe84458fb5bba1dcc1f835fe.png


从上面的代码和结果可以看出来,不管是int类型的指针p还是char类型的指针ch都可以存放n的地址并且是一样的没有发生变化。但是加上1问题就出来了,同样是地址+1为什么指针p和指针ch找到的地址不一样呢?


这正是由于指针类型不同导致的,int类型指针+1走的距离是4个字节,而char类型指针+1走的距离是1个字节,由此我们可以推论指针的类型决定了指针向前或者向后走一步有多大(距离)


总结::指针的类型决定了指针向前或者向后走一步有多大(距离)


char类型指针一步 -------------------------------- 1个字节

int类型指针一步 -----------------------------------4个字节

long long类型指针一步---------------------------8个字节

float类型指针一步---------------------------------8个字节

short类型指针一步--------------------------------2个字节


2.2指针的解引用


解引用操作符* :


int main()
{
  int n = 10;//向内存开辟一个空间给n
  int* p = &n;//将n的地址取出来放进p中。
  //那如何找到这个地址呢?如何访问这个变量呢?
  //我们可以根据地址来找到这个变量,而怎么找呢?需要解引用操作符*的帮助
  *p = 20;//*+指针变量(地址)也就是找到该地址的位置了,然后就可以访问该地址的变量了
  //p就修改成20了
  return 0;
}


上面只是简单介绍*解引用操作符,下面我要介绍下,解引用操作在不同指针类型的功能。


int main()
{
  int n = 0x11223344;//这个是16进制的表示0x是16进制的标志, 11  22  33  44 各占一个字节总共4个字节。
  char* ch = &n;
  int* p = &n;
  *p = 0;//访问p的地址也就是n  将n改成0了吗?
  *ch = 0;//访问p的地址也即是n,也把n改成0了吗?
  return 0;
}

3d39a43c6c4042ffaa3bd17571bc6cca.png


我们可以看出对指针变量p修改为0,而对指针变量ch修改却变成一个奇怪的数字,这是为什么呢?通过的对n的内存我们来看一看:


3791853f4ef9452e83cc3959613b3de6.png


在vs2022中按发f11进入调试,然后打开内存窗口查看n的内存,当箭头走向1,2时显示n的内存为 44 33 22 11(变量的内存是倒着放的,实际是11 22 33 44后面的文件管理会讲到),然后继续调试当完成3时发现44变成了00


780bfae9f8df4915a78bf7f4a9e8b39f.png


这说明什么呢?这说明对指针变量ch解引用然后访问n,只访问了一个字节的内存,所以只把44 置换成了00,那为什么会只访问一个字节呢?当然是因为它是char类型的指针啦然后让我们再看p指针的解引用。


83e238f123ab46828ebb620deaa3bb54.png


与上述操作一致,进行调试,发现当完成*p=0时,内存44 33 22 11变成了00 00 00 00,这说明什么?这说明了指针变量p解引用访问了4个字节内存,把4个字节内存都置换成0了。


a4a22b12e4b3426a838fa283f8a554a0.png


总结:


指针的类型决定了,对指针解引用的时候权限有多大,能访问几个字节。


比如:

char指针解引用只能访问1个字节

int指针解引用只能访问4个字节

double*指针解引用可以访问8个字节等


三.野指针


概念:野指针就是指针指向的位置不可知(是随机的,不正确的,没有明确限制的)


3.1野指针的成因


1.指针未初始化


int main()
{
  int* p;//局部变量指针未初始化,默认随机值
  *p = 10;//也就是一个随机值被改成了10,,这可不兴乱搞啊
  return 0;
}


2.指针访问越界


int main()
{
  int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
  int* p = arr;
  int i;
  for (i = 0;i <= 11;i++)
  {
    //当指针指向的范围超过数组arr的范围时,p就是野指针
    *(p++) = 0;
  }
  return 0;
}


3.指针指向的空间释放


这个就是指指针所维护的那个地址没了,销毁了,既然空间都销毁了,那这个指针指向谁啊,所以是个也指针。举个简单例子


:void print(int n)//进入函数内部,想一想n的生命域是什么
{
  int* p = &n;
  *p = 20;
}
int main()
{
  int n = 15;
  print(n);
  //n的生命域就是函数内部,出了函数,变量n的就不在存在,内存空间就被销毁了
  //所以在函数内部进行解引用赋值成20,但出了函数外部,那指针指向的那部分空间不存在了就变成野指针了
  printf("%d\n", n);
  return 0;
}


3.2如何规避野指针呢


1.指针要初始化

2.小心指针越界访问

3.指针指向空间释放即使之未NULL;

4.避免返回局部变量的地址

5.指针使用之前检查有效性。


int main()
{
      int *p=NULL;//不知道的指针先置成NULL;(NULL就是0的意思)
      int a=20;
      p=&a;
      if(p!=NULL)//使用之前检查有效性
      {
      *p=10;
      }
}


四.指针运算


  • 指针+ -整数
  • 指针+ -指针
  • 指针关系运算


4.1指针+ -整数


让指针走的距离


int main()
{
  int arr[10];
  int* p = NULL;
  for (p = arr;*p < arr[9];)
  {
    *(p++) = 0;//把10个数都置成0;
  }
  return 0;


4.2指针-指针


指针-指针得到的是一个整数,这个整数的绝对值是指针与指针之间元素的个数


int my_strlen(char* arr)
{
  char* ch = arr;
  while (*ch != '\0')
  {
    *(ch++);
  }
  return  ch - arr;
}
int main()
{
  char arr[] = "abcdef";
  int ret=my_strlen(arr);
  printf("%d", ret);//ret等于6
  return 0;
}


4.3指针的关系运算


后面和前面比较:


int main()
{
  int arr[5];
  int* p = NULL;//数组最后一个元素后面个内存位置的指针
  for (p = &arr[5];p > &arr[0];)
  {
    *(--p) = 0;
  }
  return 0;
}

f0246ecd97c34016be2aee17cba6e944.png


前面与后面比较:


int main()
{
  int arr[5];
  int* p = NULL;//数组最后一个元素后面个内存位置的指针
  for (p = &arr[4];p >= &arr[0];p--)
  {
    *p = 0;
  }
  return 0;
}

83d26d82d3d74d4c963c36a692f291f4.png


第二种情况在绝大多数的编译器上是可以顺利完成的,但是我们还是应该避免这样写,因为标准并不保证它可行。


标准规定:


允许指向数组元素的指针与指向数组最后的一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。


五.指针和数组


我们来看一个例子:


int main()
{
  int arr[] = { 1,2,3,4,5 };
  printf("%p\n", &arr[0]);
  printf("%p\n", &arr);
  return 0;
}


结果为:8e89788dc7d049a294ddd250d9e1777c.png


这表明数组名和数组首元素的地址是一样的;


结论:数组名就表示首元素的地址。(sizeof(数组名)和。。。除外)。


int main()
{
  int arr[]={1,2,3,4,5};
  int *p=arr;//p里存放的就是数组首元素的地址
}


既然可以把数组名当初首元素地址来看,那么用指针访问数组就有可能了:


int main()
{
  int arr[] = { 1,2,3,4,5 };
  int* p = arr;
  int sz = sizeof(arr) / sizeof(arr[0]);
  int i;
  for (i = 0;i < sz;i++)
  {
    printf("arr[%d]=%p\n", i, p+i);
  }
  return 0;
}


结果为8a50beb1409248899f8288bbf4b4fe22.png


这是访问数组每一个地址接下来我们来访问数组每一个元素,代码如下:


int main()
{
  int arr[] = { 1,2,3,4,5 };
  int* p = arr;
  int sz = sizeof(arr) / sizeof(arr[0]);
  int i;
  for (i = 0;i < sz;i++)
  {
    printf("%d ", *(p + i));
  }
  return 0;
}


结果为:


cdeaa82378e34a5491c413f48efcf23c.png


六.二级指针


指针变量也是变量,变量就有地址,那么指针变量的地址在哪呢?


把存放指针变量的地址的变量称为二级指针:


bf0eca7c1a8f4bc4a91691ff6f646502.png


对于二级指针的运算有:


  • **p可以通过解引用操作,对p中的地址寻找,能找到p,**p其实访问的就是p;


int mian()
{
   int b=10;
   int *p=&b;
   int **p2=&p;
   再找回来
   对*(*p2)解引用 找到是就是b的地址


七.指针数组


指针数组是指针还是数组呢?


答案是:数组。是存放指针的数组。


我们知道数组有整形数组,字符数组等


e2a17b7978aa4e2486773ea4f98a1d2e.png


那指针数组是怎么的?


f9065f220b674201a2edbd3b7e60c0ce.png


arr是个数组,有7个元素,每个元素都是一个整形指针。


e1127d1df6bd41a6a7efc8f0d18eaa5e.jpg

相关文章
|
6月前
|
存储 C++
C/C++指针从0到99(详解)
C/C++指针从0到99(详解)
指针的部分应用
指针的部分应用
47 0
|
6月前
|
编译器
深入理解指针(2)
深入理解指针(2)
39 2
|
程序员 C语言
C 指针
C 指针。
39 0
|
5月前
|
存储 编译器 C++
C++中的指针
C++中的指针
31 1
|
5月前
|
C++
C++指针
C++指针
|
6月前
|
存储 程序员 C++
c++指针
c++指针
35 1
|
6月前
|
存储 算法 程序员
|
6月前
|
编译器
指针(1)
指针(1)
25 0
|
存储 编译器 C++
认识C++指针
认识C++指针