【C语言】初识指针(二)

简介: 本文讲解:初识指针二。

 

你可以改变你的行为,但改变不了你想要什么——《浴血黑帮》


目录

1、指针类型

1.1指针加减(+、-)整数

1.2指针的解引用

2、野指针

2.1什么叫野指针

2.1.1指针未初始化

2.1.2指针越界访问

2.1.3指针指向的空间被释放了

2.2如何避免野指针


前言:

大家好,我是拳击哥。今天我给大家带来是初识指针第二期。本期主要讲解的是指针的类型,野指针概念,如何避免野指针的情况发生。


上一期回顾:

指针是什么?

    1. 指针是内存中一个最小单元的编号,也就是地址
    2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

    总之指针就是地址,我们可以通过&(取地址操作符)来取出一个变量在内存中的实际地址,并且可以通过指针变量修改这个变量。那么有一程序:

    #include<stdio.h>
    int main()
    {
      short num = 10;
      short* p = &num;
      *p = 20;
      printf("%d\n", num);
      return 0;
    }

    image.gif

    输出结果:20

    为啥输出20呢,请容我细细道来:

    short num = 10;在内存开辟一块空间,这块空间是num的,占两个字节。怎样得到这块空间的地址呢?我们可以通过&(取地址操作符)来得到这块空间的地址也就是num的地址。

    short* p = &num;定义一个短整型指针变量p把num的地址赋值给p,意味着指针变量p指向了num的地址。因此我可以对指针变量p进行解引用来修改num的值。

    image.gif编辑

    *p = 20; 对指针变量p进行解引用(*),然后把20赋值给*p。此时指针变量p指向的是num的地址,所以20同时也赋值给了num。

    image.gif编辑


    通过上面的例子,我们知道了指针变量是用来存放地址的变量。(存放在指针中的值都被当成地址处理)那么有疑问的是指针的一个小的单元到底是多大?(1个字节)如何进行编址?在上一期我讲到了,您可以点击下方链接进去看看:

    【C语言】初识指针(一)_拳击哥带你捶键盘的博客-CSDN博客

    image.gif编辑


    1、指针类型

    C语言中数据是有类型的如整型、字符型、浮点型等等,那么指针有自己的类型吗。准确来说是有的。我们来看一组代码:

    #include<stdio.h>
    int main()
    {
      int num = 0x11223344;
      int* p1 = &num;
      char* p2 = &num;
      printf("%p\n", &num);
      printf("%p\n", p1);
      printf("%p\n", p2);
      return 0;
    }

    image.gif

    输出三个个相同的随机地址

    012FFEB4

    012FFEB4

    012FFEB4

    以上代码证明了指针变量p1和指针变量p2指向的就是num的地址。如果通过解引用p1和p2修改num的值,会发生什么呢,我们来看两组代码:

    //代码1
    #include<stdio.h>
    int main()
    {
      int num = 0x11223344;
      int* p1 = &num;
      *p1 = 0;
      printf("%d\n", num);
      return 0;
    }
    //代码2
    #include<stdio.h>
    int main()
    {
      int num = 0x11223344;
      char* p2 = &num;
      *p2 = 0;
      printf("%d\n", num);
      return 0;
    }

    image.gif

    代码1输出结果:0

    代码2输出结果:287453952

    我们可以看到对p1进行解引用修改了num四个字节的值,对p2进行解引用只能修改num第一个字节的值。说明了指针类型是有局限性的,得按照类型来划分。 注意0x11223344是十六进制数对应的十进制数是287454200。

    指针类型决定了指针进行解引用操作时,一次性访问几个字节。如果是int*的指针一次访问四个字节,如果是char*的指针一次访问一个字节 。

    有一程序:

    #include<stdio.h>
    int main()
    {
      int num = 0x11223344;
      char* p1 = (char*)&num;
      int* p2 = &num;
      for (int i = 0; i < 4; i++)
      {
        printf("%d ", *p1);
        p1++;
      }
      printf("\n%d\n", *p2);
      return 0;
    }

    image.gif

    输出结果

    68 51 34 17

    287454020

    上述程序,for循环对p1解引用依次输出了四个数,分别是44、33、22、11对应的十进制数68、51、34、17。因为0x11223344在内存中是倒着存放的,所以输出的值也是倒着输出的。

    image.gif编辑

    对p2解引用直接输出了十六进制11223344对应的十进制数287454200

    image.gif编辑

    证明了char*的指针往后自增时访问一个字节,int*的指针访问四个字节。对应了各个类型字节数,因此证实了指针类型是有意义的,是根据数据类型大小来进行访问的步长


    1.1指针加减(+、-)整数

    指针的加减整数,就是决定指针往前或往后加减多少个字节。加法就是往后访问,减法就是往前访问。比如定义一个整型的变量,我想一个字节一个字节往后访问这时候可以定义字符型指针指向该变量,我想两个字节往后访问这时候可以定义短整型指针指向该变量。

    我们来看短整型往后加1:

    #include<stdio.h>
    int main()
    {
      int num = 0x11223344;
      short* p= &num;
      printf("%d\n", *p);
      p++;
      printf("%d\n", *p);
      return 0;
    }

    image.gif

    输出结果

    13124

    4386

    分别输出的是短整型往后加1前的值和往后加1后的值 ,十六进制3344对应的十进制是13124,十六进制1122对应的十进制是4386。可以看到短整型往后加1访问的是两个字节。

    以上是加法,减法也是一样。往前访问几个字节根据指针对应的数据类型来决定。那么访问几个字节,我们认为这是指针的步长。


    1.2指针的解引用

    详细大家在上述几个例子中已经了解到了指针解引用操作的用法,还是刚才那组程序:

    #include <stdio.h>
    int main()
    {
      int n = 0x11223344;
      char* p1 = (char*)&n;
      int* p2 = &n;
      *p1 = 0;
      printf("%d\n", n);
      *p2 = 0;
      printf("%d\n", n);
      return 0;
    }

    image.gif

    输出结果:

    287453952

    0

    对指针进行解引用得到的值是根据指针的类型来决定的,如果是字符型那只能访问一个字节,整型能访问四个字节。这看我们的用法,不同的地点不同的用法。如果有程序让我们依次访问一个整型的每个字节里面存的值,这个时候就可以用字符型指针来访问了。

    总结:

      • 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
      • 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节

      2、野指针

      今天听到一句惨绝人寰骂人的话:“你TM就是一个没有对象的野指针!”

      image.gif编辑


      2.1什么叫野指针

      概念:野指针就是指针指向的位置是不可知(随机、无方向、无法意料)的。造成野指针的情况有三种,指针未初始、指针越界访问、指针指向的空间被释放了。

      2.1.1指针未初始化

      有一代码:

      #include<stdio.h>
      int main()
      {
        int* p;
        *p = 20;
        return 0;
      }

      image.gif

      出现警告

      image.gif编辑

      局部变量指针未初始化指向的地址是随机值,以上代码定义的指针p就是一个野指针。如果我们直接给未初始化的指针解引用并赋值的话,这个值不知道放哪里去了。因为p指向的地址是未知的、随机的。我们应该这样写:

      #include<stdio.h>
      int main()
      {
        int a = 10;
        int* p=&a;
        *p = 20;
        printf("%d\n", a);
        return 0;
      }

      image.gif

      输出结果:20

      此时指针p指向了a的地址,指针就有了方向不会造成指向的地址是未知、随机的。 这样的话我再对指针p指向的地址里面内容进行修改,就没有问题。


      2.1.2指针越界访问

      数组有越界访问的情况,指针也是如此,我们来看代码:

      #include<stdio.h>
      int main()
      {
        int arry[6] = { 1,2,3,4,5,6 };
        int* p = arry;
        for(int i = 0; i <= 6; i++)
        {
          printf("%d ", *(p + i));
        }
        return 0;
      }

      image.gif

      输出结果:1 2 3 4 5 6 -858993460

      以上情况出现了指针越界的情况,arry数组只有6个元素,而指针p却访问到了下标为6的第7个元素的地址,第7个元素地址里面的值是未知的,因此造成了野指针。

      image.gif编辑


      2.1.3指针指向的空间被释放了

      有一代码:

      #include<stdio.h>
      int* test()
      {
        int a = 20;
        return &a;
      }
      int main()
      {
        test();
        int* p = test();
        printf("%d\n", *p);
        return 0;
      }

      image.gif

      输出结果:20

      虽然输出的结果是20,但是这个程序是很危险的 ,会出现警告。因为test函数在返回主函数时,test函数的栈帧已经销毁了a是一个局部变量因此也销毁了。所以test返回的地址也是一个未知的状况,也许返回的地址让指针p恰好得到了20。但是我在前面加上一段代码,就完全打破了指针p与被销毁的test函数中a的关联。

      image.gif编辑

      如果我在解引用指针p前加上一段程序:

      #include<stdio.h>
      int* test()
      {
        int a = 20;
        return &a;
      }
      int main()
      {
        test();
        int* p = test();
        printf("Hellow World\n");
        printf("%d\n", *p);
        return 0;
      }

      image.gif

      输出结果

      Hellow World

      13  

      printf函数打破指针p与a的关联,如果解引用指针p能正常的输出20就说明test函数还在内存中,反之并不能那是因为test函数在返回给主函数的已经把所有的内存还给了寄存器,也就是test函数的栈帧被回收了

      但如果没有另一块栈帧挤掉当前残存在寄存器里面的test函数栈帧,就会像上方第一个代码那样输出20。 所以这就是为啥在对指针p解引用前加上一个printf语句,指针p就输出其他的值。您应该理解了吧。

      image.gif编辑


      2.2如何避免野指针

        1. 指针初始化
        2. 小心指针越界
        3. 指针指向空间释放,及时置NULL
        4. 避免返回局部变量的地址
        5. 指针使用之前检查有效性

        1.指针初始化,指针p1初始化指向a的地址,这就是指针初始化了 ,如下代码:

        #include<stdio.h>
        int main()
        {
          int a = 10;
          int* p1 = &a;//指针初始化
          return 0;
        }

        image.gif

        给指针初始化就不会造成野指针的情况,因为指针此时是有明确的指向目标的。


        2. 小心指针越界,指针访问,比如2.1.2程序修改后,我令指针p访问的下标范围在[0,6)之间。

        #include<stdio.h>
        int main()
        {
          int arry[6] = { 1,2,3,4,5,6 };
          int* p = arry;
          for(int i = 0; i < 6; i++)
          {
            printf("%d ", *(p + i));
          }
          return 0;
        }

        image.gif

        输出结果:1 2 3 4 5 6

        指针越界跟数组越界相似,指针通过地址并对地址进行解引用来对应的值,数组则是通过下标来获取对应的值。我们只需要不让指针指向到越界的下标,就不会造成野指针。


        3.指针指向的空间被释放了,我们应该及时的把指针置为NULL。如果还要对这个指针进行操作,我们可以先对这个指针进行判断看它是否为空指针。如下方程序:

        #include<stdio.h>
        int main()
        {
          int* p = NULL;
          if(p != NULL)
          {
            printf("%d\n",20);
          }
          else
          {
            printf("%d\n", 21);
          }
          return 0;
        }

        image.gif

        输出结果:21

        上方程序就是判断指针是否为空,然后进行下一步操作。


        4.避免返回局部变量的地址,如2.1.3程序中,test函数销毁了,因此test函数里面的局部变量a也随之销毁了。因此返回的值也是无效的。

        #include<stdio.h>
        int* test()
        {
          int a = 20;
          return &a;
        }
        int main()
        {
          test();
          int* p = test();
          printf("Hellow World\n");
          printf("%d\n", *p);
          return 0;
        }

        image.gif

        输出结果

        Hellow World

        13

        Hellow World打破指针p与被销毁的a的联系,因此证实了指针p此时指向的是被销毁后的局部变量a,此时p是一个野指针。


        5.使用指针前检查指针的有效性,比如我初始化指针为NULL。这个指针为空了,代表着就不可用了。如以下方程序:

        #include<stdio.h>
        int main()
        {
          int* p = NULL;
          *p = 20;
          return 0;
        }

        image.gif

        指针p初始化为NULL,并且指针未指向任何有意义的数据此时的指针p就不可用,指针p就是一个野指针。


        以上就是今天的博客了,下期我们讲解指针运算,指针和数组,二级指针和指针数组。感谢您的观看

        image.gif编辑

        Never Give Up

        相关文章
        |
        3月前
        |
        C语言
        【c语言】指针就该这么学(1)
        本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
        64 0
        |
        1月前
        |
        存储 NoSQL 编译器
        【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
        指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
        96 3
        【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
        |
        1月前
        |
        存储 编译器 C语言
        【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
        在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
        61 9
        |
        1月前
        |
        安全 程序员 C语言
        【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
        在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
        47 7
        |
        2月前
        |
        存储 C语言
        C语言如何使用结构体和指针来操作动态分配的内存
        在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
        183 13
        |
        2月前
        |
        存储 C语言 开发者
        C 语言指针与内存管理
        C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
        68 11
        |
        2月前
        |
        存储 程序员 编译器
        C 语言数组与指针的深度剖析与应用
        在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
        |
        2月前
        |
        算法 C语言
        C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
        本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
        150 3
        |
        2月前
        |
        存储 算法 程序员
        C 语言指针详解 —— 内存操控的魔法棒
        《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
        |
        2月前
        |
        程序员 C语言
        C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
        C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
        45 1