最近在调研野指针的定位工具,对野指针有了更深入的理解,写篇文章总结下。
一、那什么是野指针?这是维基百科上的定义:
当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称迷途指针(即通常说的野指针)。
可以看出普通指针变成野指针需要满足两个条件:
1、其所指向的对象被释放或者收回,2、其本身没有做任何的修改。
// ARC环境下:
__unsafe_unretained UIView *testObj1 = [[UIView alloc] init];
// testObj1 指向的内存区域已释放,本身未做任何修改,已成为野指针,向其发消息会闪退。
__weak UIView *testObj2 = [[UIView alloc] init];
// testObj2 指向的内存区域已释放,但本身被置为nil,不会成为野指针。
需要指出一点:访问野指针本身是没有问题的,不会引起异常;只有使用野指针时才会异常(比如OC里给对象发消息),表现就是闪退。
// ARC环境下
__unsafe_unretained UIView *testObj = [[UIView alloc] init];
NSLog(@"testObj 指针指向的地址:%p 指针本身的地址:%p", testObj, &testObj);
[testObj setNeedsLayout];
// 可以看到NSlog打印不会闪退,调用[testObj setNeedsLayout];会闪退
二、什么情况下会产生野指针?
从产生过程上分析,有以下几种情况:
(1)指针变量未初始化:任何指针变量刚被创建时不会自动成为NULL指针,所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
(2)指针释放之后未置空:有时指针在dealloc或free后未赋值 NULL\nil,dealloc或free后它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。
释放后的指针应立即将指针置为NULL\nil,防止产生“野指针”。
(3)指针操作超越变量作用域:不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。
第一点主要指使用C语言时。但是给初始化的指针赋个默认值是个好习惯,也是有必要的,不同的平台和编译环境对未初始化的指针的处理是有差异的。比如ARC环境下,定义NSInteger num
,num 就可能被初始化成一个极大值。
下图是使用野指针可能出现的情况:
三、怎么检测野指针?
Xcode已经提供了比较完善的检测方法,Malloc Scribble
和Zoombie Objects
。也可以自己实现这两种检测方法。
1、实现Xcode的Malloc Scribble功能,Hook系统的free()函数,可以适用于c\c++类,标记每个要释放的对象:修改其指针指向的内存区域,在指针指向的内存区域填充数据(0x55), 使指针指向不可读的内存,再给这个对方发消息时,就会闪退。
static void (*orig_free)(void *);
void safe_free(void* p){
size_t memSiziee= malloc_size(p);
// 给指针指向的内存区域填充值,指针本身没有发生改变
memset(p, 0x55, memSiziee);
orig_free(p);
return;
}
int main(int argc, char * argv[]) {
@autoreleasepool {
// 使用fishhook库,替换系统的free(void *)函数实现
rebind_symbols((struct rebinding[1]){{"free", safe_free, (void *)&orig_free}}, 1);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
2、实现Xcode的Zoombie Objects功能,实现自己的Zoombie Object(僵尸类),然后Hook系统的- (void)dealloc方法,只适用于oc类,修改对象的 isa 指针,令其指向特殊的僵尸类。僵尸类只有isa指针,没有任何方法实现,如果这个对象再次收到消息,就会闪退。
- (void)dealloc
{
const char *className = object_getClassName(self);
char *zombieClassName = NULL;
do {
Class zombieClass = objc_getClass(zombieClassName);
objc_destructInstance(self); //关键
object_setClass(self, zombieClass);
} while (0);
if (zombieClassName != NULL)
{
free(zombieClassName);
}
}
这两种方法都是在产生野指针后,使用野指针时,让APP崩溃来发现是那里出的问题。也就是说需要尽量覆盖各种场景,才能发现野指针,并不能用扫描的方式提早发现。所以还是需要保证编码质量,从源头上避免出现野指针的问题。
参考资料:
https://blog.csdn.net/weibo1230123/article/details/81476085
https://www.jianshu.com/p/4c8a68bd066c
https://www.jianshu.com/p/8aba0ee41cd7