写程序遇到 bug 并不可怕,大部分的问题,通过简单的 log 或者 代码分析并不难找到原因所在。但是在 objective-c 编程中遇到 exc_bad_access 问题的时候,通过简单常规的手段很难发现问题。这篇文章,给大家介绍一个常用的查找 exc_bad_access 问题根源的方法。
首先说一下 exc_bad_access 这个错误,可以这么说,90%的错误来源在于对一个已经释放的对象进行release操作。举一个简单的例子来说明吧,首先看一段java代码:
public class test{
public static void main(string[] args){
string s = “this is a test string”;
s = s.substring(s.indexof(“a”),(s.length()));
system.out.println(s);
}
}
这种写法在java中很常见也很普遍,这不会产生任何问题。但是到了 objective-c 中,就会出事,考虑这个程序:
#import
int main (int argc, const char * argv[]) {
nsautoreleasepool * pool = [[nsautoreleasepool alloc] init];
nsstring* s = [[nsstring alloc]initwithstring:@”this is a test string”];
s = [s substringfromindex:[s rangeofstring:@"a"].location];//内存泄露
[s release];//错误释放
[pool drain];//exc_bad_access
return 0;
}
这个例子当然狠容易的看出问题所在,如果这段代码包含在一个很大的逻辑中,确实容易被忽略。objective-c 这段代码有三个致命问题:1、内存泄露;2、错误释放;3、造成 exc_bad_access 错误。
1, nsstring* s = [[nsstring alloc]initwithstring:@”this is a test string”]; 创建了一个 nsstring object,随后的 s = [s substringfromindex:[s rangeofstring:@"a"].location]; 执行后,导致创建的对象引用消失,直接造成内存泄露。
2,错误释放。[s release]; 这个问题,原因之一是一个逻辑错误,以为 s 还是我们最初创建的那个 nsstring 对象。第二是因为从 substringfromindex:(nsuinteger i) 这个方法返回的 nsstring 对象,并不需要我们来释放,它其实是一个被 substringfromindex 方法标记为 autorelease 的对象。如果我们强行的释放了它,那么会造成 exc_bad_access 问题。
3, exc_bad_access。由于 s 指向的 nsstring 对象被标记为 autorelease, 则在 nsautoreleasepool 中已有记录。但是由于我们在前面错误的释放了该对象,则当 [pool drain] 的时候,nsautoreleasepool 又一次的对它记录的 s 对象调用了 release 方法,但这个时候 s 已经被释放不复存在,则直接导致了 exc_bad_access问题。
那么,知道了 exc_bad_access 的诱因之一后,如何快速高效的定位问题?
1: 为工程运行时加入 nszombieenabled 环境变量,并设为启用,则在 exc_bad_access 发生时,xcode 的 console 会打印出问题描述。
首先双击 xcode 工程中,executables 下的 可执行模组,
在弹出窗口中,variables to be set in the environment,添加 nszombieenabled,并设定为 yes,点击选中复选框启用此变量。
这样,运行上述 objective-c 时会看到控制台输出:untitled[3646:a0f] *** -[cfstring release]: message sent to deallocated instance 0x10010d340
这条消息对于定位问题有很好的提示作用。但是很多时候,只有这条提示是不够的,我们需要更多的提示来帮助定位问题,这时候再加入 mallocstacklogging 来启用malloc记录。
当错误发生后,在终端执行:
malloc_history ${app_pid} ${object_instance_addr}
则会获得相应的 malloc 历史记录,比如对于上一个控制台输出
untitled[3646:a0f] *** -[cfstring release]: message sent to deallocated instance 0x10010d340
则我们可以在终端执行,结果如下:
buick-wongs-macbook-pro:downloads buick$ malloc_history 3646 0x10010d340
malloc_history report version: 2.0
process: untitled [3646]
path: /users/buick/desktop/untitled/build/debug/untitled
load address: 0×100000000
identifier: untitled
version: ??? (???)
code type: x86-64 (native)
parent process: gdb-i386-apple-darwin [3638]
date/time: 2011-02-01 15:07:04.181 0800
os version: mac os x 10.6.6 (10j567)
report version: 6
alloc 0x10010d340-0x10010d357 [size=24]: thread_7fff70118ca0 |start | main | objc_msgsend | lookupmethod | prepareformethodlookup | _class_initialize | [nsstring initialize] | objc_msgsend | lookupmethod | prepareformethodlookup | _class_initialize | nxcreatemaptablefromzone | malloc_zone_malloc
—-
free 0x10010d340-0x10010d357 [size=24]: thread_7fff70118ca0 |start | main | objc_msgsend | lookupmethod | prepareformethodlookup | _class_initialize | _finishinitializing | free
alloc 0x10010d340-0x10010d357 [size=24]: thread_7fff70118ca0 |start | main | -[nsplaceholderstring initwithstring:] | objc_msgsend | lookupmethod | prepareformethodlookup | _class_initialize | _class_initialize | [nsmutablestring initialize] | objc_msgsend | lookupmethod | prepareformethodlookup | _class_initialize | nxcreatemaptablefromzone | malloc_zone_malloc
—-
free 0x10010d340-0x10010d357 [size=24]: thread_7fff70118ca0 |start | main | -[nsplaceholderstring initwithstring:] | objc_msgsend | lookupmethod | prepareformethodlookup | _class_initialize | _class_initialize | _finishinitializing | free
alloc 0x10010d340-0x10010d35f [size=32]: thread_7fff70118ca0 |start | main | -[nscfstring substringwithrange:] | cfstringcreatewithsubstring | __cfstringcreateimmutablefunnel3 | _cfruntimecreateinstance | malloc_zone_malloc
这样就可以很快的定位出问题的代码片段了,注意输出的最后一行,,,这行虽然不是问题的最终原因,但是离问题点已经很近了,随着它找下去,八成就会找到问题。