views:

814

answers:

5

I've got a very simple line of code in Objective-C:

if ((selectedEntity != nil) && [selectedEntity isKindOfClass:[MobileEntity class]])

Occasionally and for no reason I can tell, the game crashes on this line of code with an EXC-BAD-ACCESS. It usually seems to be about the time when something gets removed from the playing field, so I'm guessing that what was the selectedEntity gets dealloc'd, then this results. Aside from being impossible to select exiting Entities (but who knows, maybe this isn't actually true in my code...), the fact that I am specifically checking to see if there is a selectedEntity before I access it means that I shouldn't be having any problems here. Objective-C is supposed to support Boolean short-citcuiting, but it appears to not be EDIT: looks like short-circuiting has nothing to do with the problem.

Also, I put a @try/@catch around this code block because I knew it was exploding every once in a while, but that appears to be ignored (I'm guessing EXC-BAD-ACCESS can't be caught).

So basically I'm wondering if anybody either knows a way I can catch this and throw it out (because I don't care about this error as long as it doesn't make the game crash) or can explain why it might be happening. I know Objective-C does weird things with the "nil" value, so I'm guessing it's pointing to some weird space that is neither an object pointer or nil.

EDIT: Just to clarify, I know the below code is wrong, it's what I was guessing was happening in my program. I was asking if that would cause a problem - which it indeed does. :-)

EDIT: Looks like there is a fringe case that allows you to select an Entity before it gets erased. So, it appears the progression of the code goes like this:

selectedEntity = obj;
NSAutoreleasePool *pool = ...;
[obj release];
if (selectedEntity != nil && etc...) {}
[pool release];

So I'm guessing that because the Autorelease pool has not yet been released, the object is not nil but its retain count is at 0 so it's not allowed to be accessed anyway... or something along those lines?

Also, my game is single-threaded, so this isn't a threading issue.

EDIT: I fixed the problem, in two ways. First, I didn't allow selection of the entity in that fringe case. Second, instead of just calling [entities removeObjectAtIndex:i] (the code to remove any entities that will be deleted), I changed it to:

//Deselect it if it has been selected.
if (entity == selectedEntity)
{
    selectedEntity = nil;
}

[entities removeObjectAtIndex:i];

Just make sure you are assigning nil to the variable at the same time you release it, as jib suggested.

+6  A: 

This has nothing to do with short circuiting. Objective-C eats messages to nil, so the check for selectedEntity != nil isn't necessary (since messages-to-nil will return NO for BOOL return types).

EXC_BAD_ACCESS is not a catchable exception. It is a catastrophic failure generally caused by trying to follow in invalid pointer.

More likely than not, whatever object selectedEntity points to has been released before the code is executed. Thus, it is neither nil nor a valid object.

Turn on NSZombies and try again.

If your app is threaded, are you synchronizing selectedEntity across threads properly (keeping in mind that, in general, diddling the UI from secondary threads is not supported)?


Your post was edited to indicate that the fix is:

//Deselect it if it has been selected.
if (entity == selectedEntity)
{
    selectedEntity = nil;
}

[entities removeObjectAtIndex:i];

This fixes the issue because the NSMutableArray will -release objects upon removal. If the retain count falls to zero, the object is deallocated and selectedEntity would then point to a deallocated object.

bbum
You pointed me in the right, direction, I think. Can you see if my edit makes sense to you?
Eli
A: 

i just read this http://developer.apple.com/mac/library/qa/qa2004/qa1367.html which indicated that the error you are getting is a result of over-releasing the object. this means that altough selectedEntity is nill, you released it to many times and it just not yours to use anymore..

Nir Levy
It's not always an over-release problem, and isn't in this case (I never call release or retain on the object, it is simply added to and then removed from an NSMutableArray).
Eli
A: 

Put a breakpoint on OBJC_EXCEPTION_THROW and see where it is really being thrown. That line should never throw a EXC_BAD_ACCESS on its own.

Are you perhaps doing something within the IF block that could cause the exception?

Marcus S. Zarra
+4  A: 

if an object (selectedEntity) has been released and dealloc'd it is not == nil. It is a pointer to an arbitrary piece of memory, and deferencing it ( if(selectedEntity!=nil ) is a Programming Error (EXC_BAD_ACCESS).

Hence the common obj-c paradigm:-

[selectedEntity release]; selectedEntity = nil;

That's just what I needed to know. Thanks a lot!
Eli
That isn't correct. selectedEntity != nil will **never** cause an EXC_BAD_ACCESS. The EXC_BAD_ACCESS will only happen when dereferencing the pointer, which will happen when the object is messaged.
bbum
Yes, true, you can always check the value of something without causing an error, you just can't message it.
Eli
A: 
Maven
I should have been clearer - I know the above code is a problem, that's a very simplified version of what I suspected was happening. I was not asking if it was correct code. :-)
Eli