Saturday, February 11, 2006

Messaging nil in Objective-C

Sending a message to a nil object doesn't make much sense in many programming languages. For example, if you do this in Java you'll get the dreaded NullPointerException. But sending a message ("sending a message" in Objective-C is similar to "calling a method" in other OO languages) to a nil object is defined, okay, and incredibly useful in Objective-C. Actually, one of the most common coding idioms in objective-C is:


Foo *foo = [[Foo alloc] init];

which creates a Foo instance by sending the +alloc message to the Foo class, then sending the -init method to the returned instance. However, if +alloc fails and returns nil, the -init method will be sent to a nil object which simply ends up setting foo to nil (which is probably exactly what we'd want to happen anyway).


I'd like to see an example


OK, let's write some sample code to test this.

// Compile with: gcc -o nil nil.m -framework Foundation
#import <Foundation/Foundation.h>

@interface Foo : NSObject
- (NSString *)sayHi;
@end

@implementation Foo
- (NSString *)sayHi {
return @"Hello, World!";
}
@end

int main() {
Foo *foo = nil;
NSLog(@"Greeting = %@", [foo sayHi]);
return 0;
}

2006-02-11 20:49:26.372 nil[3406] Greeting = (null)

So, we can see that when we send the message -sayHi to a nil pointer the return value is nil.


How does this work?


The compiler turns message calls like [targetObject someSelector] into a C function call like objc_msgSend(targetObject, someSelector). So, to figure out what this returns we simply need to figure out what objc_msgSend() does when its first argument is nil. Well, we can download the source for the Objective-C runtime from Apple here. The file we're interested in is objc-msg-ppc.s (yes, it's in PPC assembly). If we search for "ENTRY _objc_msgSend" we'll see the function we're looking for. The comments are very useful in this file and we can pretty easily see that it checks if its first argument (passed in register r3), which happens to be the target object, is nil and if so it does a few other things and eventually returns nil. And since C functions on PowerPC chips return integer and pointer values in register r3 nothing needs to be done; the function simply returns and the result is that the caller thinks the function (or "message") returned nil. And since integers are returned the same way as pointers, sending a message that returns an int will return 0, simply because nil is #define'd to be 0 (/usr/include/objc/objc.h).


But what if the method returns a float?


Let's see...

#import <Foundation/Foundation.h>

@interface Foo : NSObject
- (float)blah;
@end

@implementation Foo
- (float)blah {
return 5.0;
}
@end

int main() {
Foo *foo = nil;
NSLog(@"blah = %f", [foo blah]);
return 0;
}

2006-02-11 21:20:20.948 nil[3441] blah = 0.000000

So, it looks like messages that return a float return 0.0 like we'd expect. Wrong! Change the test code as indicated:

void g(float f) {}
int main() {
g(2.0);

2006-02-11 21:22:47.094 nil[3452] blah = 2.000000

Ah-ha! Now the return value for messaging our nil object was 2.0! So, it looks like the return value in this case is whatever value happens to be in the appropriate floating point register.


Interesting! So what does it mean?


All this neat stuff means that it *is* safe to send a message to nil when:

  • The method is declared to return a pointer

  • The method is declared to return any integer value less than or equal to sizeof(void *) (32 on a 32-bit machine)



and it is NOT safe when

  • The method returns any floating point value

  • An integer value > sizeof(void *)



Also, it's usually *not* safe to message nil when the message returns a structure.

Conclusion



The ability to send messages to nil is an incredibly cool and powerful feature of Objective-C, but it may not always do what you intend. I've read that Apple is trying to standardize the behavior of messaging nil (they'll likely guarantee that it will "always" return a zero value), but this is currently not the case.

For more info check out these docs.

*DISCLAIMER: I've simplified a few things here to make this more understandable. I also did not cover issues related to messaging nil on Intel chips. Maybe I'll leave some of these things for future posts. If you have questions about any of this, or simply think I'm wrong about something, please post a comment. I'll get back to you as soon as possible. I love to discuss this stuff :-)

1 comment:

Anonymous said...

Just a pointer to one more discussion of this topic:

http://ridiculousfish.com/blog/archives/2005/05/29/nil/