C语言中的野指针和空指针

在测试中,有时候开机正常,有时候跑几分钟死机。测试bug中经常遇到正常跑某个流程,是正常的,但是在特定条件下,总是遇到程序崩溃,业务中断。也会遇到同样的代码,在这次运行正常,下次就会崩溃。今天涉及的空指针就会造成问题现象稳定复现,野指针会出现BUG飘忽不定。为了减少这种烦恼,本文将介绍什么是空指针和野指针,他们有哪些危害,平时我们又该如何预防呢?

一、定义

1、空指针(NULL Pointer)--是合法的指针状态

指针变量被赋值为NULL(本质是宏,一般等价于(void*)0),代表指针不指向任何合法有效的内存地址(明确表明当前指针没有指向有效地址 )。

NULL是C标准规定的特殊常量地址0,C++新增nullptr

2、野指针(Wild Pointer)

指针变量保存了一个随机、未知、不受控制的非法内存地址,没有初始化,或者指向的内存已经被释放。

c 复制代码
int *p;//局部变量,未初始化,内存残留随机值---》野指针
int *p=malloc(4);
free(p);//free之后不会自动变为NULL,p依旧保存原来地址---》野指针

二、危害

1、空指针带来的困扰

(1)程序直接崩溃(确定性故障)

Linux产生错误(SIGSEGV),Windows程序闪退。服务端程序一旦崩溃,业务中断;嵌入式设备直接死机、重启。

(2)崩溃堆栈清晰,但是容易忽略边界场景

很多场景只是在特定条件下指针才变成NULL,正常流程跑不出来。

(3)增加防御代码负担

所有接收指针的函数都要加if(p==NULL)判断,如果漏了,就会埋下崩溃隐患。

空指针造成的故障稳定,只要充分测试,大多可以提前发现。

2、野指针带来的困扰

(1)Bug难以复现

未定义行为(UB):同样的代码,这次运行正常,下次崩溃。

野指针保存的随机地址,每次运行不一样。

刚好访问只读内存,程序直接崩溃;

刚好访问进程合法内存:静默运行,无任何报错。

(2)隐性数据纂改(无声的破坏性BUG)

野指针碰巧指向程序其他变量、数组、栈上数据,进行写操作---不会崩溃,但是悄悄修改其他变量的值。

eg:变量值莫名被篡改,日志看不出异常,业务逻辑错乱,计算结果错误。

(3)破坏栈、堆内存,引发连锁崩溃

eg:A函数报错,根源在完全不相干的B函数野指针。

改写堆内存管理块:后续调用malloc/free直接崩溃

破坏栈上函数返回地址:程序跑飞,出现诡异的非法指令崩溃--崩溃点不是野指针出错的地方,崩溃堆栈完全误导人。

(4)无法通过简单判空防御

判空只能防御空指针,防御不了野指针。

(5)重复free释放

重复free触发堆破坏,程序直接崩溃。

(6)嵌入式环境危害大

嵌入式没有完善内存保护,野指针可能改写寄存器、外设地址,导致硬件异常,设备失控

三、出现后现象表现

1、空指针操作现象--不能对空指针引用*p

Linux:段错误Segmentation fault(SIGSEGV),程序直接崩溃。

Windows触发访问异常,程序闪退,如下

单纯if(p==NULL)判断空指针完全没有问题,只有解引用才报错。

2、野指针操作现象

很多人以为出错是空指针,实际大量线上崩溃、诡异逻辑错误都是野指针导致,有如下几种可能情况

直接崩溃 :访问操作系统保护地址,报段错误

静默运行,没有报错 :刚好指向程序自己可读内存,读写成功,潜伏bug

破坏其他变量参数 :改写了别的合法变量的值,造成莫名奇妙的数据错乱

间歇性崩溃:有时候跑正常,有时候崩溃。

四、野指针产生的根源

1、指针未初始化

局部指针变量不赋初值,栈内存残留随机垃圾值(野指针)

c 复制代码
int *p;
*p=5;//给未知地址赋值了5

2、内存free后,指针没有NULL

c 复制代码
#include<stdio.h>
#include<windows.h>
#include<math.h>
int main(){
    SetConsoleOutputCP(65001);
    SetConsoleCP(65001);
    int *p=malloc(4);
    free(p);//p仍然保存原来的地址,变为野指针
    printf("p的地址%p\n",p);
    if(p!=NULL)
    {
        *p = 10;//访问已经释放的内存
    }

} 

3、指针越界、指向临时变量(栈内存失效)

c 复制代码
#include<stdio.h>
#include<windows.h>
#include<math.h>
int* func()
{
    int a=10;
    return &a;
}
int main(){
    SetConsoleOutputCP(65001);
    SetConsoleCP(65001);
    int *p;
    p=func();
    printf("p的地址%p\n",p);
    if(p!=NULL)
    {
        *p = 10;//访问已经释放的内存
    }

} 

func中的a是局部变量,函数结束栈回收,返回的指针变为野指针,编译只是警告,仍可编译成功

五、预防

1、编码规范

指针一律初始化,使用指针之前先确定位置,free后置NULL

使用指针解引用之前,先判断是否为NULL

函数接收指针参数,优先做空指针防御检查

禁止返回局部栈变量的地址

2、尽量减少裸指针,有条件使用封装

3、工具辅助

linux:valgrind检测内存泄漏、野指针、重复free

gcc编译加警告:gcc -wall -wextra,尽可能捕获未初始化指针

使用地址消毒器AddressSanitizer:-fsanitize=address,快速定位野指针、越界访问。

总结:空指针明确指向"无地址",可控;

野指针指向随机未知地址,极度危险。

相关推荐
C++ 老炮儿的技术栈2 小时前
从 Qt Designer 属性编辑器的层级可以看到继承链
c语言·数据库·c++·qt·sqlite·visual studio
二十雨辰3 小时前
[Java]-Spring面试题
java·开发语言
萧瑟余晖3 小时前
Java深入解析篇二十二之虚拟线程
java·开发语言
油丶酸萝卜别吃4 小时前
Java 集合类全景介绍
java·开发语言
水饺编程4 小时前
AT&T 汇编语言学习笔记开篇语
c语言·汇编·c++
Iruoyaoxh4 小时前
类和对象~
开发语言·c++
程序喵大人4 小时前
【C++进阶】STL算法与函数对象 - 04 find、count和any_of把查询写成意图
开发语言·c++·算法
Livia要学习4 小时前
Python装饰器
开发语言·python
一直都在5724 小时前
LangChain4j精讲
开发语言·人工智能
wuminyu4 小时前
虚拟线程底层ForkJoinPool的工作窃取算法机制
java·linux·c语言·jvm·c++