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,快速定位野指针、越界访问。

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

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

相关推荐
CoderIsArt29 分钟前
C#中UI 线程与 Dispatcher
开发语言·ui·c#
AOI小白新手上路1 小时前
江科协51单片机 17-2 红外遥控电机调速实验调试总结
单片机·嵌入式硬件·51单片机
AI情绪识别开源3 小时前
检信 ALLEMOTION OS 加密打包可执行程序 — 全面测试报告版本: v1.3功能测试 / 性能测试 /
开发语言·数据结构·人工智能·功能测试
oshan20123 小时前
零基础STM32入门教程--10-PWM 呼吸灯
stm32·单片机·嵌入式硬件
「QT(C++)开发工程师」4 小时前
C++ auto 用法详解
开发语言·c++
OPEN-F4 小时前
C++STL教程:容器适配器与实用工具
开发语言·c++
yaoxin5211234 小时前
507. Java 反射 - 在 BeanFactory 中实现依赖注入
java·开发语言
OPEN-F4 小时前
C++模板教程:变参模板、折叠表达式与SFINAE
java·开发语言·c++
有点。4 小时前
C++二叉搜索树进阶
开发语言·c++