首页 > 其他分享 >一文读懂野指针

一文读懂野指针

时间:2022-12-05 09:13:26浏览次数:43  
标签:一文 char 读懂 地址 内存 NULL 变量 指针

一、引子

        我们都知道对指针( Pointer)的操作,实际上是对计算机内存地址的操作,通过访问内存地址实现间接访问该地址中保存的数据。其实就是CPU的寻址方式中的间接寻址。简单概括正常使用指针时的3个步骤为:

  • 定义指针变量
  • 绑定指针即给指针变量赋值
  • 解引用即间接访问目标变量
    通过一个简单的例子来看这3个步骤的实现:
1 int a = 5;
2 //定义指针变量p
3 int *p;
4 //绑定指针,就是给指针变量赋值,指向另一个变量a(指针的用途就是指向别的变量)
5 p = &a;
6 //将6放入p所指向的那个变量的空间中,这里就是a的空间
7 *p = 6;

        可以看出,在定义指针变量p时,未初始化p,这个时候的p为随机值,此时解引用p是没有意义的,内存随机值的空间是否有效我们也不得而知。

        绑定指针就是将变量a的地址赋值给指针变量p,此时p就有了意义,明确了内存中访问的具体空间位置,p是指向变量a的空间的,变量a是有具体内容的,因此指针必须给它赋值才能解引用它。

        给指针变量p赋值实际上是在变量a前加一个“&”符号,这个符号是取地址符,&a就是指变量a的地址,编译器在给每个变量分配出内存空间,并将a与这块的内存空间地址绑定。这个地址只有编译器知道,而程序员并不知道编译器随机给这段空间分配什么随机地址值。程序员要获取或操作这个地址时,就需要使用取地址符。

        由上述分析看来,给p赋予了变量a地址的值是一个合法的,在内存中明确的地址值,这个值是受控的,同时通过访问指针间接访问该地址中保存的数据也是受控的,p就是一个正常的指针。

        相反,如果指针指向了内存中不可用的区域,或者是指针的值是非法的随机值也就是非正常内存地址,那么这个指针就是不受控的,同时通过访问指针间接访问该地址中保存的数据也是不受控的,同时是不可知的,此时这个指针就是野指针(Wild Pointer)。

二、需要明确的一点

        野指针不同于空指针,所谓空指针,是给指针变量赋NULL值,即:

1 int *p = NULL;

        所谓NULL值在C/C++中定义为:

1 #ifdef __cplusplus         // 定义这个符号表示当前是C++环境中
2 #define NULL 0             // 在C++中NULL为0
3 #else
4 #define NULL (void *) 0    // 在C中的NULL是强制类型转换为void *的0
5 #endif

        可以看出,给p赋值NULL值也就是让p指向空地址。在不同的系统中,NULL并不意味等于0,也有系统会使用地址0,而将NULL定义为其他值,所以不要把NULL和0等同起来。你可以将NULL通俗理解为是空值,也就是指向一个不被使用的地址,在大多数系统中,都将0作为不被使用的地址,因此就有了这样的定义,C或者C++编译器保证这个空值不会是任何对象的地址。

        void *表示的是“无类型指针”,可以指向任何数据类型,在这里void指针与空指针NULL区别:NULL说明指针不指向任何数据,是“空的”;而void指针实实在在地指向一块内存,只是不知道这块内存中是什么类型的数据。

        空指针的值是受控的,但并不是有意义的,我们是将指针指向了0地址,这个0地址就是作为内存中的一个特殊地址,因此空指针是一个对任何指针类型都合法的指针,但并不是合理的指针,指针变量具有空指针值,表示它处于闲置状态,没有指向任何有意义的内容。我们需要在让空指针真正指向了一块有意义的内存后,我们才能对它取内容。即:

1 int a = 5;
2 int *p = NULL;
3 p = &a;

         NULL指针并没有危害,可以使用if语句来判断是否为NULL。

三、一些典型的error

        我们要知道单纯的从语言层面无法判断一个指针所保存的地址是否是合法的,等到程序运行起来,配合硬件的内存实际地址,才能发现指针指向的地址是否是你想要让它指向的合理空间地址。在日常编码过程中有一些导致野指针或者内存溢出的错误编码方式:

1、指针变量未初始化

        任何指针在被创建的时候,不会自动变成NULL指针,因此指针的值是一个随机值。这时候去解引用就是去访问这个地址不确定的变量,所以结果是不可知的。

1 void main()
2 {
3     char* p;
4     *p = 6;  //错误
5 }

2、使用了悬垂指针

        在C或者C++中使用malloc或者new申请内存使用后,指针已经free或者delete了,没有置为NULL,此时的指针是一个悬垂指针。

        free和delete只是把指针所指的内存给释放掉,并不会改变相关的指针的值。这个指针实际仍然指向内存中相同位置即其地址仍然不变,甚至该位置仍然可以被读写,只不过这时候该内存区域完全不可控即该地址对应的内存是垃圾,悬垂指针会让人误以为是个合法的指针。

1 void main()
2 {
3     char* p = (char *) malloc(10);
4     strcpy(p, “abc”);
5     free(p);  //p所指的内存被释放,但是p所指的地址仍然不变
6     strcpy(p, “def”); // 错误
7 }

3、返回栈内存指针或引用

        在函数内部定义的局部指针变量或者局部引用变量不能作为函数的返回值,因为该局部变量的作用域范围在函数内部,该函数在被调用时,由于局部指针变量或者引用已经被销毁,因此调用时该内存区域的内容已经发生了变化,再操作该内存区域就没有具体的意义。

 1 char* fun1()
 2 {
 3    char* p = "hello";
 4    return p;
 5 }
 6 
 7 char* fun2()
 8 {
 9     char a = 6;
10     return &a;
11 }
12 
13 void main()
14 {
15     char* p1 = fun1(); //错误
16     char* p2 = fun2(); //错误
17 }

4、指针重复释放

 1 void fun(char* p, char len)
 2 {     
 3     for(char i = 0; i < len; i++)
 4     {
 5         p[i] = i;
 6     }      
 7     free(p);
 8 }
 9 
10 void main()
11 {
12     char * p1 = (char *)malloc(6 * sizeof(char)); 
13     fun(p1, 6); 
14     free(p1);  //重复释放指针导致错误  
15 }

5、数组越界

        使用的数组长度超过了定义的数组长度。

1 void main()
2 {
3     int a[6]; 
4     for(int i = 0; i<=6; i++) //错误
5 }

6、内存分配后未初始化

1 void main()
2 {
3     char* p = (char*)malloc(6); 
4     printf(p); //p未初始化
5     free(p);
6 }

7、使用的内存大小超过了分配的内存大小

1 void main()
2 {
3     char* p = (char*)malloc(6); 
4     for(int i = 0; i <= 6; i++)  //错误
5     {
6         p[i] = i;
7     }
8     free(p);
9 }

四、避免错误的注意点

        1、在定义指针变量时,要将其值置为NULL,即 char *p = NULL。

        2、在指针使用之前,需要给指针赋具体值,就是将其绑定一个可用地址空间让其有意义,即p = &a。

        3、在使用指针前,需要判断指针为非NULL,只有非NULL的指针才有意义。即判断if(p != NULL)。

        4、free或者delete指针后,需要将指针值置为NULL。

        5、malloc和free,new和delete注意配对使用,当 malloc或new次数大于 free或delete 时,会产生内存泄漏;需要‍防止多次重复free或者delete,当malloc或new 次数小于free或delete时,程序有可能会崩溃。

        6、使用malloc或new分配内存后,需要初始化,同时在使用时注意不要超过分配的内存大小空间。

        7、在哪个函数里面进行的 malloc或new ,就在哪个函数里面 free或delete,不要跨函数去释放动态的内存空间。

        8、不要将局部指针变量,局部引用变量或局部数组作为函数的返回值。

        9、使用数组时一定要注意定义的数组大小,防止数组越界;或者在定义数组时可以不定义数组长度,即int a[]。

        10、在定义有指针操作相关的函数时必须指定长度信息,即void fun(char* p, char len)。

BTW:

        最后根据以上的讨论,再结合以下网友的总结,我们可以更好的理解下野指针在实际程序中的危害:

        a、指向不可访问(操作系统不允许访问的敏感地址,譬如内核空间)的地址,结果是触发段错误,这种算是最好的情况了。

        b、指向一个可用的、而且没什么特别意义的空间(譬如我们曾经使用过但是已经不用的栈空间或堆空间),这时候程序运行不会出错,也不会对当前程序造成损害,这种情况下会掩盖你的程序错误,让你以为程序没问题,其实是有问题的。

   c、指向了一个可用的空间,而且这个空间其实在程序中正在被使用(譬如说是程序的一个变量x),那么野指针的解引用就会刚好修改这个变量x的值,导致这个变量莫名其妙的被改变,程序出现离奇的错误。一般最终都会导致程序崩溃,或者数据被损害。这种危害是最大的。


更多技术内容和书籍资料获取敬请关注微信公众号“明解嵌入式”

标签:一文,char,读懂,地址,内存,NULL,变量,指针
From: https://www.cnblogs.com/Sharemaker/p/16951429.html

相关文章

  • 指针基础知识(中)
    上一次我们讲的那个指针基础知识上的时候说过指针两边的类型要一致,否则会出错,但是我经过查阅别的资料,发现是可以的,并且不管你是用什么类型的指针来接收定义的值的地址,都是同......
  • 【C语言】指针Ⅰ--- 概念、前言、内存、地址与指针。
    ......
  • 4.指针和引用的区别详解
    前言指针和引用在形式上很好区别,在C++中相比于指针我们更喜欢使用引用,但是它们的使用场景又极其类似,它们都能直接引用对象,对对象进行处理,那么究竟为什么会引入引用?什么时......
  • 9.【C语言详解】指针
    指针是什么指针是什么?指针理解的2个要点:指针是内存中一个最小单元的编号,也就是地址;平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量;指针就是地址,......
  • 14.【C语言进阶】指针
    简介指针的概念指针是个变量,用来存储地址。指针的大小只与是64位平台还是32位平台有关,与指针类型无关。指针类型决定了指针的解引用权限和读取方式。指针+-正数与指......
  • 玩转双指针
    一、算法简介双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。也可以延伸到多个数组的多个指针。若两个指针指向同一数组,遍历方向相同且不会相交,则也称......
  • C 字符串指针与字符串数组
    在程序中定义的字符串无论是对于字符串指针还是字符串数组,都会将字符串放到静态存储区在程序开始运行的时候,对于数组来说,先分配内存空间,然后静态存储区中的字符串字面量......
  • go指针逃逸对结构体元素集合法(增法)
    packagemainimport("awesomeProject/logger""fmt""github.com/opencontainers/runtime-tools/filepath""os")typeGoodsstruct{IDuintMonTsstr......
  • 一文搞定SSL证书的所有创建问题
    创建SSL证书是一个很无聊的过程,偏偏有时候它又很重要,但是无聊的事情实在让人乏味,以后一定会忘记,那就写一篇比较完全的文章留作以后复制粘贴吧。虽然TLS跟SSL不是......
  • 一文教会你如何在内网搭建一套属于自己小组的在线 API 文档?
    Hello,大家好,我是阿粉,对接文档是每个开发人员不可避免都要写的,友好的文档可以大大的提升工作效率。阿粉最近将项目的文档基于Gitbook和Gitlab的Webhook功能的在内网......