写程序遇到 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 <Foundation/Foundation.h>

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 下的 可执行模组,

wpid-Screenshot2011-02-01at2.56.50PM-2011-02-1-13-17.png

在弹出窗口中,Variables to be set in the environment,添加 NSZombieEnabled,并设定为 YES,点击选中复选框启用此变量。

wpid-Screenshot2011-02-01at2.58.17PM-2011-02-1-13-17.png

这样,运行上述 Objective-C 时会看到控制台输出:

Untitled[3646:a0f] *** -[CFString release]: message sent to deallocated instance 0x10010d340

wpid-Screenshot2011-02-01at3.07.23PM-2011-02-1-13-17.png

这条消息对于定位问题有很好的提示作用。但是很多时候,只有这条提示是不够的,我们需要更多的提示来帮助定位问题,这时候再加入 MallocStackLogging 来启用malloc记录。

wpid-Screenshot2011-02-01at3.03.08PM-2011-02-1-13-17.png
当错误发生后,在终端执行:

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

这样就可以很快的定位出问题的代码片段了,注意输出的最后一行,,,这行虽然不是问题的最终原因,但是离问题点已经很近了,随着它找下去,八成就会找到问题。

当然,EXC_BAD_ACCESS 的定位方法还有很多,随着具体问题的不同而不同。欢迎各位讨论。

 

转自 http://ibuick.com/buick2011820/index.php/archives/objective-c-exc_bad_access

 

posted @ 2011-02-24 11:16 小胡子&Violet 阅读(336) 评论(1) 编辑

Apple 在iphone和iad上的巨大成功,不得不让人反思.

反思什么?反思企业的自主化.

Apple从处理器到操作系统到销售渠道,基本都是公司的产业和线路,很少使用第三方,真正做到了自己的企业和产品自己做主,不容易受限于第三方,他能控制产品从创意到研发到生产直至到用户手中,都能精确控制.

我觉得在这方面Apple真的让我相当钦佩...

posted @ 2011-02-24 10:43 小胡子&Violet 阅读(313) 评论(1) 编辑
摘要: 上讲中我们了解了一些Linq中要用到的一些新机制,这讲中我们来初步了解下Linq的查询,由于此篇内容比较简单明了,微软的说明很清楚,很容易理解,所以决定讲内容讲直接引用MSDN中的教程(不是人懒啊,看来看去这次内容没什么地方需要重点讲解的!大家别丢鸡蛋啊!)。阅读全文
posted @ 2008-06-24 00:42 小胡子&Violet 阅读(1293) 评论(1) 编辑
摘要: 本文讨论了如何使用Windows Installer技术发布.NET程序,以及如何使用native代码判断目标机器上是否安装有.NET Framework; 如果没有,将自动安装.NET Framework然后安装作者自己的.NET程序。 阅读全文
posted @ 2008-06-18 16:27 小胡子&Violet 阅读(722) 评论(0) 编辑
摘要: 在上一讲中,我们介绍了Linq,并且知道了Linq可以提供SQL Server数据库,XML文档、ADO.NET数据集、.NET集合、文件、字符串等查询,那么今天再来学习一个新的关键词“var”。阅读全文
posted @ 2008-06-17 01:24 小胡子&Violet 阅读(2956) 评论(5) 编辑
摘要: 前言:
许多朋友在编写以数据库为中心的应用程序是时常会因为调用数据库以及维护数据的关系等而烦恼,繁杂的SQL字符串语句,SQL注入检测,语法,字符长度等,有些时候为了在程序上更方便使用还要写一些看似不重要只为了方便的存储过程。现在好了从.NET Framework 3.5版本开始我们可以使用面向对象的方式访问数据库了。图1显示了Linq的基本使用方式,看了以后是不是觉得似曾相识啊。阅读全文
posted @ 2008-06-16 20:27 小胡子&Violet 阅读(5378) 评论(2) 编辑
摘要: 代码如下:String.Format=function(){if(arguments.length==0)return"";if(arguments.length==1)returnarguments[0];varreg=/{(\d+)?}/g;varargs=arguments;varresult=arguments[0].replace(reg,function($0,$1){returnar...阅读全文
posted @ 2008-06-10 11:01 小胡子&Violet 阅读(678) 评论(0) 编辑
摘要: 一个能检查JavaScript语法的Js脚本,经常受Js错误困扰的朋友可以下载试试!阅读全文
posted @ 2008-06-07 23:02 小胡子&Violet 阅读(231) 评论(0) 编辑
摘要: 在网站设计的时候,应该注意css样式兼容不同浏览器问题,特别是对完全使用DIV CSS设计的网,就应该更注意IE6 IE7 FF对CSS样式的兼容,不然,你的网乱可能出去不想出现的效果!阅读全文
posted @ 2008-06-07 22:45 小胡子&Violet 阅读(257) 评论(0) 编辑