The No-Nil Trap

Just ran into an interesting issue while programming: my app decided it would randomly start crashing. Love when that happens. As it turns out, the cause had to do with a very nuanced characteristic of C (and Objective-C). I call it (perhaps incorrectly, but it works for me): the No-Nil Trap.

When you initialize an object with its -init method (or equivalent), the default NSObject implementation initializes all instance variables to nil, as a convenience. However, whenever you declare a variable elsewhere, this auto-initialization does not occur: rather, like with any C variable, it creates a block of memory in the stack sizeof(id) big. If you don’t initialize that pointer with a value, it still points to that address in memory.

So let’s take a relatively common example:

// ... exciting code ...
if (myData)
	[myData release];

Assuming myData is an instance variable (and every other time you release it, you set it back to nil), this should go off without a hitch. If the value referred to by myData is a non-false-evaluating quantity, release it.

Note: this is usually superfluous, since it’s safe to call -release on nil — it’s safe to call anything on nil, it always returns nil. As long as you’re good about nil-ing out your instance variables after you release them, you don’t need to check if they’re non-nil first.

Now let’s modify the example slightly:

NSData *myData;
// ... exciting code ...
if (myData)
	[myData release];

In this snippet, myData is a local variable rather than an instance variable, and believe it or not, this will possibly cause an application to intermittently crash. The reason is that we didn’t initialize myData, which was assigned an address anyway. If we were to insert a few lines of code in between the variable declaration and the conditional, that space could become filled with something else. Something else that might not belong to you. And we all know what happens when you try to dispose of memory that doesn’t belong to you.

The correct way to write this would be:

NSData *myData = nil;
// ... exciting code ...
if (myData)
	[myData release];

It’s a simple fix, but it’s one that’s easy to forget, especially since it’s not a problem you need to worry about with auto-initialized instance variables.

This particular one caused me some massive headaches, because it was being called on the return trip of a Distributed Objects call, so the debugger wasn’t able to pinpoint exactly where the problem was occurring. And for that, NSLog (and selective commenting) is now, and always will be, your friend. ;)

Comments

  1. Tom

    Tom said…

    For me, this is why writing most of my code in a managed language like Java rules. No way to access uninitialized memory like in C. ;)

    28 Jan 2010 at 8:40pm

Add a Comment