objective-cxcodevariables

Local variable being stored over different function calls. weird


I'm bewildered by whats happening. It's more of a trick question I don't know the answer to.

I have the following function inside my main.m of an Objective-C program.

int incrementCounter(){
     int counter;
    counter+=2;
    return  counter;
}

and inside my main.m

 NSLog(@"Counter: %d",incrementCounter());
 NSLog(@"Counter: %d",incrementCounter());
 NSLog(@"Counter: %d",incrementCounter());

The weird thing is one mac book(don't know the Xcode version ) when I executed this the output was:

2013-05-28 19:16:27.131 SOQ[4923:707] Counter: 1
2013-05-28 19:16:27.132 SOQ[4923:707] Counter: 3
2013-05-28 19:16:27.132 SOQ[4923:707] Counter: 5

My questions here:
1. How did counter get initialized to 1? (when i use static it get initialized to zero)
2. How is the value being stored over successive method calls?
3. When I check for the memory location of this variable for different executions of the program it remains the same. How does that happen?

And when I executed the same piece of code in another mac(lion Xcode 4.2.1) it gives the following output:

2013-05-28 19:16:27.131 SOQ[4923:707] Counter: 32769
2013-05-28 19:16:27.132 SOQ[4923:707] Counter: 32769
2013-05-28 19:16:27.132 SOQ[4923:707] Counter: 32769

My questions here:
1. How does this behavior change from mac to mac? //I was thinking different compiler versions but it was both using the same(not sure though) . Is there any other way?
2. How did the counter get initialized to 32767? I was thinking garbage value, but isn't the objective-c compiler supposed to initialize primitive like int to zero? And I'm getting the same 32769 over and over again. How?

Maybe I'm missing out on something basic. Any pointers?


Solution

  • In your incrementCounter function you are using counter which is an uninitialised variable, so the behaviour is 'undefined' - which means what you're seeing is perfectly valid behaviour (in that any behaviour would be valid since the behaviour is undefined).

    So as for what is actually happening (or could potentially be happening, depending on the compiler and runtime implementations):

    1-1: It's undefined what the initial value will be, but it might end up always being the same for multiple executions if the object loader behaves in a deterministic way and just happens to leave a certain bit-pattern in that memory prior to beginning execution of your program, or even just by accident.

    1-2: When incrementCounter is called some memory on the stack is allocated (the next available chunk, on the top of the stack), then incrementCounter increments and returns it. Then NSLog is called, using the same stack-memory (but perhaps a different size). NSLog happens to not trample on that value (probably because the compiler puts it in a place used for return values and NSLog doesn't have a return value). Then incrementCounter is called again, the same stack-memory is re-used and the value just happens to not have been trampled. Try implementing a second otherIncrementCounter method in exactly the same way to see if they are both sharing the same memory for that return value, also take a look at &counter in both methods, it's probably the same.

    1-3: The local address-space that your program uses when it is run as a process is local to that process. A different program writing to the same address won't trample your process's memory. A kernel implementation can map this memory however it likes, for example it could use the same starting-point for the stack address space for all its processes.

    2-1 & 2-2: See 1-1 and 1-2 above. Perhaps a different build of the kernel, object loader, OS, etc. In this case perhaps NSLog is doing something with that value, perhaps using it temporarily, to set it to 32767 each time. The implementation of NSLog may have changed or may just be compiled with a different compiler version on that machine - it's in a shared library that's loaded and linked at run-time, not compiled into your executable.

    In general it's fairly pointless to wonder about these things, there are too many reasons that things could be working as they are, but it's useful to know how compilers and stacks work so I hope my answers above give you inspiration to learn about that a bit more. However, when programming (and not learning about compilers) simply don't rely on undefined behaviour, turn on lots of warnings and treat them as errors so you don't ever accidentally depend on the implementations of compilers, runtimes, or library implementations.