Thread 1: EXC_BAD_ACCESS

IOS 開發中,如果提前釋放一個指針的內存,在以後還繼續使用這個指針,那麼程序會立刻 crash 掉,而且很難有報錯信息,我以前都是靠猜測去判斷錯誤的原因,我們應該利用工具去找到錯誤的地方,然後快速准確的定位到錯誤地方,及其錯誤原因,最後進行改進。

    其實 iOS 控制台提供這種機制,如果你選擇 debug 模式(必須在這個模式下),在程序 crash 之後,在控制台輸入 bt,就可以顯示 crash 堆棧:

    Program received signal:  “EXC_BAD_ACCESS”.
    warning: Unable to read symbols for /Developer/ios4.2.1/Platforms/iPhoneOS.platform/DeviceSupport/4.2.1 (8C148)/Symbols/Developer/usr/lib/libXcodeDebuggerSupport.dylib (file not found).
    (gdb) bt
    #0  0x33a06464 in objc_msgSend ()
    #1  0x3139de2e in -[UIImageView setImage:] ()
    #2  0x00009ecc in -[RoundMenuView touchesEnded:withEvent:] (self=0x29e140, _cmd=0x316b1a7b, touches=0x2e1050, event=0x2424f0) at /Users/wangjun/workspace/iphone/Classes/RoundMenuView.m:130
    #3  0x313b1354 in -[UIWindow _sendTouchesForEvent:] ()
    #4  0x313b0cce in -[UIWindow sendEvent:] ()
    #5  0x3139bfc6 in -[UIApplication sendEvent:] ()
    #6  0x3139b906 in _UIApplicationHandleEvent ()
    #7  0x31eecf02 in PurpleEventCallback ()
    #8  0x304236fe in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ ()
    #9  0x304236c2 in __CFRunLoopDoSource1 ()
    #10 0x30415f7c in __CFRunLoopRun ()
    #11 0x30415c86 in CFRunLoopRunSpecific ()
    #12 0x30415b8e in CFRunLoopRunInMode ()
    #13 0x31eec4aa in GSEventRunModal ()
    #14 0x31eec556 in GSEventRun ()
    #15 0x313cf328 in -[UIApplication _run] ()
    #16 0x313cce92 in UIApplicationMain ()
    #17 0x00002da2 in main (argc=1, argv=0x2fdff44c) at /Users/wangjun/workspace/iphone/main.m:19
    kill
    quit

    利用堆棧信息,就可以准確的定位到錯誤地方。

MallocStackLogging <wbr>的設置方法 <wbr>查找 <wbr>EXC_BAD_ACCESS <wbr>問題根源的方法
MallocStackLogging <wbr>的設置方法 <wbr>查找 <wbr>EXC_BAD_ACCESS <wbr>問題根源的方法

  寫程序遇到 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 下的 可執行模組

在彈出窗口中,Variables to be set in the environment,添加 NSZombieEnabled,並設定为 YES,點擊選中复選框启用此變量。


    這样,運行上述 Objective-C 時會看到控制台輸出:Untitled[3646:a0f] *** -[CFString release]: message sent to deallocated instance 0x10010d340

這條消息對於定位問題有很好的提示作用。但是很多時候,只有這條提示是不夠的,我們需要更多的提示來幫助定位問題,這時候再加入 MallocStackLogging 來启用malloc記錄。

 當錯誤發生後,在終端執行:

shell  malloc_history ${App_PID} ${Object_instance_addr}

輸入: malloc_history 3646 0x100110d340  

    則會獲得相應的 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

    這样就可以很快的定位出問題的代碼片段了,注意輸出的最後一行,,,這行雖然不是問題的最終原因,但是離問題點已經很近了,隨着它找下去,八成就會找到問題。

 

下面還有一個更極端的方法:

有時程序崩溃根本不知錯誤發生在什麼地方。比如程序出現EXEC_BAD_ACCESS的時候,雖然大部分情況使用設定 NSZombieEnabled環境變量可以幫助你找到問題的所在,但少數情況下,即使設定了NSZombieEnabled環境變量,還是不知道程序崩溃在什麼地方。那麼就需要使用下列代碼進行幫助了:



#ifdef _FOR_DEBUG_
-(BOOL) respondsToSelector:(SEL)aSelector {
    printf("SELECTOR: %s\n", [NSStringFromSelector(aSelector) UTF8String]);
    return [super respondsToSelector:aSelector];
}
#endif

你需要在每個object的.m或者.mm文件中加入上面代碼,並且在other c flags中加入-D _FOR_DEBUG_(記住請只在Debug Configuration下加入此標記)。這样當你程序崩溃時,Xcode的console上就會准確地記錄了最後運行的object的方法。



直接用Category覆蓋NSObject的這個方法可能會更好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"thread 1: exc_bad_access code" 是指在程序运行时发生了“访问非法内存”的错误。这种错误通常是由于代码中对一个不存在或不可访问的内存地址进行读取或写入操作引起的。 当一个线程尝试访问一个无效的内存地址时,操作系统会报告一个异常(exception),通常称为“exc_bad_access”。这个异常的错误代码(code)可能会提供更多关于错误的具体信息。 常见的导致“exc_bad_access code”错误的情况包括: 1. 空指针引用:尝试访问一个指向空地址的指针。 2. 野指针引用:尝试访问一个已经释放或不再有效的内存。 3. 数组越界访问:尝试访问一个数组之外的元素。 4. 访问已释放的对象:尝试访问一个已经释放的对象的成员变量或方法。 5. 内存溢出:尝试访问操作系统未分配给程序的内存空间。 要解决“thread 1: exc_bad_access code”错误,可以通过以下方式: 1. double-check代码中的指针或数组访问,确保没有空指针引用或越界访问。 2. 确保在访问动态分配的内存(如对象或数组)之前,它们已经正确地分配和初始化。 3. 如果有多个线程访问同一块内存,请使用同步机制(如互斥锁)来避免竞争条件和数据访问冲突。 4. 调试程序以查找并纠正任何内存错误,可以使用调试器或打印出相关变量的值进行排查。 总之,“thread 1: exc_bad_access code”表示程序出现了访问非法内存的错误,需要审查代码并修复相关问题才能消除错误。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值