最近在調(diào)研野指針的定位工具,對野指針有了更深入的理解,寫篇文章總結(jié)下。
一、那什么是野指針?這是維基百科上的定義:
當(dāng)所指向的對象被釋放或者收回,但是對該指針沒有作任何的修改,以至于該指針仍舊指向已經(jīng)回收的內(nèi)存地址,此情況下該指針便稱迷途指針(即通常說的野指針)。
可以看出普通指針變成野指針需要滿足兩個條件:
1、其所指向的對象被釋放或者收回,2、其本身沒有做任何的修改。
// ARC環(huán)境下:
__unsafe_unretained UIView *testObj1 = [[UIView alloc] init];
// testObj1 指向的內(nèi)存區(qū)域已釋放,本身未做任何修改,已成為野指針,向其發(fā)消息會閃退。
__weak UIView *testObj2 = [[UIView alloc] init];
// testObj2 指向的內(nèi)存區(qū)域已釋放,但本身被置為nil,不會成為野指針。
需要指出一點(diǎn):訪問野指針本身是沒有問題的,不會引起異常;只有使用野指針時才會異常(比如OC里給對象發(fā)消息),表現(xiàn)就是閃退。
// ARC環(huán)境下
__unsafe_unretained UIView *testObj = [[UIView alloc] init];
NSLog(@"testObj 指針指向的地址:%p 指針本身的地址:%p", testObj, &testObj);
[testObj setNeedsLayout];
// 可以看到NSlog打印不會閃退,調(diào)用[testObj setNeedsLayout];會閃退
二、什么情況下會產(chǎn)生野指針?
從產(chǎn)生過程上分析,有以下幾種情況:
(1)指針變量未初始化:任何指針變量剛被創(chuàng)建時不會自動成為NULL指針,所以,指針變量在創(chuàng)建的同時應(yīng)當(dāng)被初始化,要么將指針設(shè)置為NULL,要么讓它指向合法的內(nèi)存。
(2)指針釋放之后未置空:有時指針在dealloc或free后未賦值 NULL\nil,dealloc或free后它們只是把指針?biāo)傅膬?nèi)存給釋放掉,但并沒有把指針本身干掉。此時指針指向的就是“垃圾”內(nèi)存。
釋放后的指針應(yīng)立即將指針置為NULL\nil,防止產(chǎn)生“野指針”。
(3)指針操作超越變量作用域:不要返回指向棧內(nèi)存的指針或引用,因為棧內(nèi)存在函數(shù)結(jié)束時會被釋放。
第一點(diǎn)主要指使用C語言時。但是給初始化的指針賦個默認(rèn)值是個好習(xí)慣,也是有必要的,不同的平臺和編譯環(huán)境對未初始化的指針的處理是有差異的。比如ARC環(huán)境下,定義NSInteger num
,num 就可能被初始化成一個極大值。
下圖是使用野指針可能出現(xiàn)的情況:
三、怎么檢測野指針?
Xcode已經(jīng)提供了比較完善的檢測方法,Malloc Scribble
和Zoombie Objects
。也可以自己實(shí)現(xiàn)這兩種檢測方法。
1、實(shí)現(xiàn)Xcode的Malloc Scribble功能,Hook系統(tǒng)的free()函數(shù),可以適用于c\c++類,標(biāo)記每個要釋放的對象:修改其指針指向的內(nèi)存區(qū)域,在指針指向的內(nèi)存區(qū)域填充數(shù)據(jù)(0x55), 使指針指向不可讀的內(nèi)存,再給這個對方發(fā)消息時,就會閃退。
static void (*orig_free)(void *);
void safe_free(void* p){
size_t memSiziee= malloc_size(p);
// 給指針指向的內(nèi)存區(qū)域填充值,指針本身沒有發(fā)生改變
memset(p, 0x55, memSiziee);
orig_free(p);
return;
}
int main(int argc, char * argv[]) {
@autoreleasepool {
// 使用fishhook庫,替換系統(tǒng)的free(void *)函數(shù)實(shí)現(xiàn)
rebind_symbols((struct rebinding[1]){{"free", safe_free, (void *)&orig_free}}, 1);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
2、實(shí)現(xiàn)Xcode的Zoombie Objects功能,實(shí)現(xiàn)自己的Zoombie Object(僵尸類),然后Hook系統(tǒng)的- (void)dealloc方法,只適用于oc類,修改對象的 isa 指針,令其指向特殊的僵尸類。僵尸類只有isa指針,沒有任何方法實(shí)現(xiàn),如果這個對象再次收到消息,就會閃退。
- (void)dealloc
{
const char *className = object_getClassName(self);
char *zombieClassName = NULL;
do {
Class zombieClass = objc_getClass(zombieClassName);
objc_destructInstance(self); //關(guān)鍵
object_setClass(self, zombieClass);
} while (0);
if (zombieClassName != NULL)
{
free(zombieClassName);
}
}
這兩種方法都是在產(chǎn)生野指針后,使用野指針時,讓APP崩潰來發(fā)現(xiàn)是那里出的問題。也就是說需要盡量覆蓋各種場景,才能發(fā)現(xiàn)野指針,并不能用掃描的方式提早發(fā)現(xiàn)。所以還是需要保證編碼質(zhì)量,從源頭上避免出現(xiàn)野指針的問題。
參考資料:
https://blog.csdn.net/weibo1230123/article/details/81476085
http://www.lxweimin.com/p/4c8a68bd066c
http://www.lxweimin.com/p/8aba0ee41cd7