views:

325

answers:

2

This is driving me nuts. I just uploaded my first app to the App Store for the first time, and of course, now my app is exploding left and right. When I build with release build configuration, I randomly get an EXC_BAD_ACCESS after banging on the app for some variable amount of time. I turned Zombies back on, but the problem doesn't appear to be over-releasing a variable, as I don't get a message about sending a message to a released variable.

The error does always appear in the same spot in my code--it looks like I'm trying to retain a variable that hasn't been initialized properly. I haven't the slightest idea how I could be doing that.

But here's the weird thing--if I build in debug release configuration, IT NEVER CRASHES. I can bang on the thing all day long, and it's rock solid. I build with Release configuration, and it gets all intermittently crashy.

Looking at the Build settings in the two configurations, there aren't that many differences. In debug, the GCC 4.0 Optimization level is "None", while in Release, it's "Fastest, Smallest." If I switch the optimization level in Release to "None," the app behaves itself. Does anyone have a clue what I should be looking for to fix this? ALternately, how many bad things happen if I Distribute with No optimization?

Thanks for any advice!

UPDATE: Dang! I really need a magic debug tool for memory errors. I've been working this problem for the last day or so. I added an 'exercise' method that would reliably generate a crash and started looking for the area of code that was causing it. The app would usually crash the 3rd-5th time I performed one particular type of operation. The only thing that was different about that type of operation was the value I returned from an accessory method. The accessory method generally returned NSDecimalNumbers with values of 0-3, but there was one special case when I was returning an NSDecimalNumber with a non-integer value. I would test the result of the accessory method looking for the non-integer value. I changed the special case to return an NSDecimalNumber with a value of -1 instead of the non-integer value, and I can no longer make the app crash.

Basically, the only change I made was switching from [[NSNumber numberWithDouble:num] decimalValue] --> crash to [[NSNumber numberWithInteger:num] decimalValue] --> no crash

It's a little but more complicated than that, but not much.

Now, I'm happy that the app no longer crashes... but the change I made does not fill me with confidence, as the old bits of code did not appear to be "broken" in any way. So, while my app doesn't crash anymore, it's not because I "fixed" it--I just changed some random thing and now it works. 8^(

UPDATE: Debugger does not spit out a stack trace as it usually does when a program crashes. When it crashes, the debug console outputs the following:

Loading program into debugger…

[... copyright stuff...]

This GDB was configured as "i386-apple-darwin".warning: Unable to read symbols for "/System/Library/Frameworks/UIKit.framework/UIKit" (file not found).

warning: Unable to read symbols from "UIKit" (not yet mapped into memory).

warning: Unable to read symbols for "/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics" (file not found).

warning: Unable to read symbols from "CoreGraphics" (not yet mapped into memory). Program loaded.

sharedlibrary apply-load-rules all

Attaching to program: `/Users/...', process 10066.

Re-enabling shared library breakpoint 1

Cannot access memory at address 0x4

Cannot access memory at address 0x4

(gdb)

+1  A: 

If only it were as easy as turning on Zombies and you could always find your over-releases... There are lots of these kinds of errors that Zombies can't detect. There is no magic debug tool for memory errors; only careful programming (and there are patterns that make these errors much more uncommon, and much easier to debug when they do occur).

Optimized code can shake free lots of things that don't show up in unoptimized code. It does suggest slightly that it may be a local variable rather than an ivar, but perhaps not. It may just be timing; being faster may make a race condition go the other way more often.

If you can get it to crash, take a look of in the stack trace as a first step of course.

There's nothing deeply wrong with distributing without optimization, but it's just masking the problem. There's a coding error in there. Optimization isn't breaking your code. Your code is broken.

There's a good discussion of debugging memory problems here. The #1 rule is that you must use accessors. They will save you much heartache, so hopefully you already do this. I provide some other pointers in my short discussion of memory management rules.

Rob Napier
Thank you for your comments. I appreciate the advice.
ChadK
+1  A: 

I'd suggest cranking up the warning level on your compiler and see if anything pops up. Open up your project settings and enable every 'warning' option you can find. Or, find the project setting named Other C Flags and add the flags -Wall -Wextra.

You're going to get a LOT of white noise for things like signed/unsigned mismatches, possible loss of precision, etc., that you can (usually) safely ignore. However, there may be some key warnings that pop up that you definitely should NOT ignore: things like making a pointer from an integer without a cast (or vice-versa). If you're not 100% sure you can ignore the warning, fix the code so the warning goes away.

Adam Rosenfield
Signed/unsigned comparison can really burn you in surprising ways. It's worth getting your signs correct, and seldom very hard. The precision thing usually doesn't create as many problems, but it's so easy to fix that you should just turn on the warning and fix it (often you need to add "f" to your constants to note that they're floats and not doubles). After "thou shalt use accessors," the second law that dramatically improved my code and reduced my debugging time was "Treat Warning as Errors."
Rob Napier
True, you can get burned badly with signed/unsigned mismatches, but quite often they're harmless, such as "for(int i = 0; i < foo.size(); i++)", where foo.size() returns a size_t.
Adam Rosenfield