






















二级指针,也叫指针的指针,或者Out Parameters,可以用来改变一个指针的地址值,由于在Objective-C里面方法、函数不支持返回多个值,所以经常用二级指针实现这个功能,比如NSFileManager的- (BOOL)removeItemAtURL:(NSURL *)URL error:(NSError **)error方法,就可以让方法在内部创建error后传出。
最近在Debug代码的时候,注释掉了一个带有二级指针参数的方法内部所有代码,然后在Release环境下安装运行,结果居然Crash了,猛然想起好像以前同事也遇到过,仔细检查了下,感觉代码是没有问题的,所以继续深究,新建了一个空的工程,重现了这个EXC_BAD_ACCESS的Crash,代码非常简单,如下:
1 |
|
经测试,在Xcode 8.0、以及最新的8.2.1稳定版下,Release环境中,都会Crash,放到iOS工程里面,模拟器、真机也会。但是,在Xcode7下就没有问题。
首先,从代码本身上来看,是没有问题的,带有二级指针的方法testFunc:是空的方法,虽然传进来了NSMutableArray **array,但是没有对其赋值。
runTest方法也只是创建了一个NSMutableArray对象,然后取了指针的指针,调用了两次testFunc:,然后打印,按理来说是没有任何副作用的。
所以,直接从代码上看,发现不了问题的原因。
直接看代码没有用,那就用clang重写出C++代码分析,命令行对main.m执行clang -rewrite-objc main.m,得到C++重写后的代码,在文件的最后,就能找到testFunc:和runTest的C++代码,如下:
1 |
|
从C++代码来看,也没有可能会导致EXC_BAD_ACCESS问题的释放、dealloc之类的代码,继续!
重写出C++代码无法解决,就只能让Hopper出马了,毕竟“汇编面前,没有秘密”=。=,以x86_64架构二进制为例。
先来看testFunc:的:

跟预想的一致,没有任何“有效代码”。
再看runTest方法:

为了方便分析,把反编译出来的汇编代码分了段:
NSMutableArray new创建array变量,保存在r15寄存器上testFunc:retain,然后release,rbx保存array,r15释放testFunc:release,再retain,即释放了rbx,然后又retain,这里明显有问题NSlog打印为了进一步验证,用Hopper的反汇编生成Objective-C代码功能进行验证,生成的代码如下:
1 |
|
release和retain的顺序错误
Hopper生成的代码跟汇编分析基本一致,至此,可以看出,唯一“不正常”的代码就是第5段的“先release,再retain”,这会导致array变量提前释放,以至于后面再次retain无效,NSLog的时候,出现访问已被释放的变量,于是出现EXC_BAD_ACCESS崩溃。
现在已经基本可以确定问题了,但是为什么Xcode7就不会Crash?反编译一探究竟。
相同的代码,Xcode7 Release环境编译出可执行二进制文件,反编译后,用Hopper生成Objective-C代码,然后跟Xcode8的做对比,如下:

可以看出,Xcode8编译后,release和retain的顺序错了,导致了array变量提前被释放,继而EXC_BAD_ACCESS错误。而Xcode7编译的一切正常。
现在可以大胆猜测,EXC_BAD_ACCESS的原因就是Xcode8的编译错误。
在编译上面的代码时,Xcode8不能正确的处理array变量作为二级指针传入testFunc空函数后的release、retain顺序,导致array被提前释放,最终产生EXC_BAD_ACCESS错误。
目前已经给Xcode提了Bug report,等待苹果的回复~
所有的代码、Xcode7和8编译出的二进制可执行文件、Hopper的文件,打包放在了Github上:https://github.com/zekunyan/OutParameterPointerCrashOnXcode8
汇编面前,没有秘密!=。=
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。