一、c/c++的指针
对于很多学习和使用c/c++语言进行编程的开发者来说,指针的使用始终是一个难点和痛点。可以夸张一些的说,指针使用的能力可以从某种角度体现出开发者c/c++编程的能力。
从上层应用到IO操作再到内存管理,进而到操作系统的底层实现,指针的应用无处不在。像变量指针、句柄指针、函数指针等等。
二、野指针
野指针,Dangling Pointer,很多资料上也称悬空指针。可能在有的资料上,把二者区分开来,从更细的角度进行了对比。比如野指针倾向于“野”,内存未初始化不可控;而悬垂指针更倾向于使用释放的内存地址 。也就是说野指针是未“开垦”未使用过的,而悬垂指针是指已“开垦”过的,使用但现在无效了。
但一般来说,可以把二者统称为一回事,说直白一些就是无效的内存指针(地址)。大家可以根据自己的理解去明白其中的内存机理即可,不必纠结这些细节。看一个基础的例子:
int *wildPtr_ ;//野指针
int *p = new int(100);
delete p;
int *dlingPtr = p;//悬垂指针
三、产生的原因
本文将统一将二者称为野指针。一般来说野指针产生的原因主要有以下几种:
这也是在开发中为什么反复强调一定要初始化变量的一个重要原因。这其中就包括指针
这是典型的悬垂指针应用的场景,使用未置空的指针再不做判断
这个也好理解,就是指针已经释放掉了或受限访问
这种情况一般比较少见,分配内存没有成功或出现异常
这种也是一种很常见的情况,其实和超作用域使用类似
这种是最复杂的一种情况,在内存池或多线程操作中很常见。其中一个提前释放掉了内存,其实线程还在使用
这就和看病一样,先找到病根儿,才能更好的有针对性的解决问题。
四、风险和危害
野指针的风险和危害非常明显。它的直接表现,往往是程序直接崩溃(Crash)。但从实际应用来看,它可能导致更多的问题:
对无效内存读写,往往导致程序的当场崩溃,甚至引起整个系统的蓝屏崩溃
这种比较奇异,比如在操作指针时,其数据非预期的不断变化,但程序运行整体正常。这种问题最难定位,但一般来说也比较少见
这也是野指针使用的最大风险和危害,将关键的数据改写甚至改写了系统地址。另外,对于一些数据存储也可能导致无法正确的存储相关数据
这对于安全相关开发的人来说,是一个重要的切入点,利用指针将数据强行写入保护地址空间或内存对齐后未使用的地址空间,从而可以达到非法的目的
五、如何预防野指针
通过上面的分析其实就可以知道如何避免野指针的操作了。在实际的开发中,可以从三个层面对其进行预防:
一)代码层面
二)编译层面
三)运行时层面
六、相关工具
对于c/c++来说,除了Review等直接人工处理的方式外,最好是借助使用相关的内存检查工具。在这一方面,c/c++已经提供了相当多的工具。
除了上面提到的可以融入编译的相关的工具(ASan)外,还可以使用内存分析工具cppcheck、valgrind、内存泄露的工具Deleaker等。也就是说,内存相关的工具非常多,大多可以用来检查野指针。不同的平台、不同的大机构一般都会提供此类的工具。无论是哪种平台一般可以将此类工具分为两类:
一般在程序运行时利用工具进行监视获取异常行为和错误,从而检测内存问题如野指针。像刚刚提到的Sanitizers、Valgrind等都属于此类型的工具
这种其实和人工Review本质是一样的,通过各个单元的引用、作用域、限制等各方面的分析,找到内存潜在的问题。象Cppcheck、Clang-Tidy等都属于此类型的工具
七、总结
内存安全无小事。特别是现在移动和互联网高度发达的情况下,一个简单的指针使用Bug就可能引发远程攻击或木马植入。所以开发者不但要从代码级别重视野指针,更要从安全的角度重视野指针。






