Welcome to MSDN Blogs Sign in | Join | Help

ToddHa's WebLog

Musings, notions, thoughts about technology and software.

News

  • Disclaimer
    The information in this weblog is provided "AS IS" with no warranties, and confers no rights. This weblog does not represent the thoughts, intentions, plans or strategies of my employer. It is solely my opinion. Inappropriate comments will be deleted at the authors discretion. All code samples are provided "AS IS" without warranty of any kind, either express or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose.
Assumptions are the mother of all (part 2).

In my own time, I've been learning Objective-C for fun. I'm not sure why, perhaps I didn't feel I spent enough time in front of a computer. But I digress.

One of the interesting things I've learned is that the following code works.

Note that I assume we have a class named ArbitraryObject, and that it has a method that takes no parameters entitled foo.

 

Objective-C

ArbitraryObject * obj;
obj = nil;
[obj foo];

Interesting, this code doesn't crash. In comparison, the following languages all crash when you try to do the equivalent.

 

C#

ArbitraryObject obj = null;
obj.foo();

This is the easy one. We get a NullReferenceException thrown.

 

Java

ArbitraryObject obj = null;
obj.foo();

This is another easy one. We get a NullPointerException thrown.

 

Python

obj = None
obj.foo()

Since we don't have to type what obj is, we get an AttributeError talking about how the NoneType object doesn't have an attribute of foo. Not exactly the same as before, but close enough.

 

C++

ArbitraryObject * obj = 0;
obj->foo();

Note that this isn't the entire code required to get the program to crash (and I'm not sure what spurred me to prove to myself that it wouldn't work). The MS VC++ compiler was optimizing my defects away in different ways. I had to add an instance variable to the ArbitraryObject class, assign it in the constructor, and have foo return the variable. Then in the calling code I printf'd it. But I'm ignoring this for simplicity.

But regardless, it's an access violation error.

 

What we've proved is that Objective-C works very differently from how other programming languages work. There are some pros and cons of this, depending on your perspective. It's just important to know how things work.

A couple notes :

  1. I use nil, null, 0, and None interchangeably. 
  2. I'm assuming the compiler hasn't optimized your (possibly intentional) errors away.
  3. I really have nothing against any programming language or technology, so please don't assume that I'm some [arbitrary technology/programming langauge] basher or fanboy (even though Objective-C is primarily an Apple language, and Apple is a competitor to Microsoft). This is just another example of where you might think things work one way, and work a different way completely.
  4. I find this particular example fascinating.

Performance

Comparing Objective-C performance to C#, Java, and Python isn't really fair as they all have to be translated into machine language at runtime.

However the C++ comparison is fair. What I'm assuming is happening in Objective-C (without diving into PPC assembly) is that there's a check before each function call that says, "Hey, if this address is nil then jump over this block of code that sets up for and executes the function call." So assumedly you have a some extra instructions every time you make a function call. Not a big deal, especially these days where we have a bazillion clock cycles per second, but definitely something that you need to think about if you're trying to squeeze every cycle out of the processor.

Program Flow

This is the main root problems I would assume. I'm going to use C# to flesh out the code, but you can easily translate it to Objective-C. More on this after the code.

Let's go back to our example code and flesh it out a bit in order to prove a point.

public class ArrayWrapper
{
    private static int [] array = new int[] { 1, 2, 3, 4 };
    private static int index = 10;

    public static SetIndex(int index)
    {
        ArrayWrapper.index = index;
    }

    public int GetIndexedValue()
    {
        // note that this crashes if index is the default value
        return ArrayWrapper.array[ArrayWrapper.index];
    }
}

public class ArbitraryObject
{
    // we don't have to define an empty constructor, but will anyways!
    public ArbitraryObject() { }
    
    public void foo()
    {
        ArrayWrapper.SetIndex(2);
    }

}

And now for the driver code function.

public void DriverFunction(ArbitraryObject obj)
{
    obj.foo();
   
    // here we have assumed that obj.foo() did it's job and set the
    // index of the ArrayWrapper to a valid value
    Console.WriteLine(ArrayWrapper.GetIndexedValue().ToString());
}

Ok, so this seems like a really stupid example, but it's point is to show that you're assuming that the ArbitraryObject class worked in order for the rest of the function to work.

Let's run some tests. These are based on whether or not obj.foo( ) worked. I'm not going to translate this to Objective-C, but you can easily see what happens.

C# Tests
Valid instance of Arbitrary Object : Wrote out "3" to the console.
Null reference : NullReference exception at line xxxx.

Objective-C Tests
Valid instance of Arbitrary Object : Wrote out "3" to the console.
Null reference : Out of bounds error.

Ok, so we still get an error in Objective-C. And it's relatively easy to debug what's happening. But if the user gets this, the app will have crashed. And they'll get upset. Arguably, they'd get upset in C# too.

But what if the default index was valid? Like 0? We wouldn't crash, but we'd get incorrect results. Which is a LOT harder to debug. But doesn't crash for the user. Maybe it affects the final results that they're looking it for. Maybe it doesn't.

My point to all this is that this introduces a lot of subtleties to debugging something that you have to think about when writing or debugging the code. I'm not saying that it's bad at all. Sometimes you don't care about if the line fails or not -- and you don't have to write a bunch of function calls prefixed with if (reference != null).

Another reason to not make assumptions about the way things work (especially in new programming languages). :)

Posted: Thursday, April 26, 2007 2:33 AM by toddha

Comments

David M. Kean said:

C# used to output 'call' instead of the 'callvirt' IL opcode (which has an implicit 'this' null check) for non-virtual calls, so in the early betas of .NET, you would found that as long the method you called on a null reference did not touch the 'this' pointer, it would continue without error.

# April 26, 2007 2:43 AM

Rosyna said:

I suggest you take a gander at the Objective-C language reference at http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/index.html

It talks a lot about the innards of it all, if you're interested.

# April 26, 2007 8:29 PM

rogper said:

Objective-C calls are really like SendMessage() and sending to null receiver (the object in Objc terminology) is by convention returning 0; messages and methods are not the same in formal sense. However, the null pointer problem appears again for function arguments.

# June 30, 2007 3:50 PM
Anonymous comments are disabled
Page view tracker