![]() |
| |||||||
|
Tutorials
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int main (int argc, const char * argv[]) { | |||||
| while (1 == 1) { |
|||||
| NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSString *theString = [NSString stringWithString: @"blurk"]; [theString release]; [pool release]; sleep(2); |
|||||
| } return 0; |
|||||
| } | |||||
Now, select "Build and Run", the button that looks like a hammer behind a terminal. You'll get the following result in the window that appears:
OutLauncher has exited due to signal 10 (SIGBUS).
Congratulations - You've just crashed your first program! To see where things went wrong, let's use the debugger. Set a breakpoint at the top of your code by clicking in the striped area to the left of the code editing area:
Now, hit the "Build and Debug" option, which looks like a hammer behind a spray can. This time, instead of running to completion, your program will be loaded into the debugger, and execution will halt at the breakpoint you set, with the debug window becoming active. To continue running the code line by line, hit the icon on the top right of this window that looks like an arrow above parenthesis. Once you advance far enough, two things should become apparent. The first is that the program crashed while executing the line where we released our Autorelease pool. The second is that, in the pane on the upper left of the debug window, you'll find the details about what was actually happening at the time of the crash - the key line to note is "NSPopAutoreleasePool". This is an important error to remember, since in most cases you won't be explicitly getting rid of your own Autorelease Pools. As a result, these errors will appear whenever the application disposes of a pool for you. In practice, the errors will be appear random, so you have to recognize them by the appearance of NSPopAutoreleasePool.
So, what's the cause of the error? The string wasn't created with an alloc, and it hadn't been given a "retain" message, so it was placed in the Autorelease pool. When sent a release, the memory holding the string was invalidated, even though the Autorelease pool still expects to deal with it. As a result, when the pool was released, it tried to clear theString, which no longer existed. If you're unsure of the retain status of an object, you can always check the status of an object by sending it a "retainCount" message - change the code to read:
NSString *theString = [NSString stringWithString: @"blurk"];
int tempInt;
tempInt = [theString retainCount];
[theString release];
Start debugging this; as you step through, watch the upper right pane of the debug window: you can track all of your variables as they change, and the most recently changed will be highlighted in red. Note that the retain count of theString is 1 - a retain count of zero mean that the object's no longer valid (and asking for the retain count of such an object would cause the program to crash again).
So, how to fix this? The easiest way is to get rid of the "release" message. If you did so, you'd find that the program could run indefinitely without error. An alternative would be to send the string a retain message when you create it:
NSString *theString = [[NSString stringWithString: @"blurk"] retain];
Or to explicitly allocate the string, which acts like a retain message:
NSString *theString = [[NSString alloc] initWithString: @"blurk"];
Let's see what happens when we try combining these. Make the code in main read:
NSString *theString = [[NSString alloc] initWithString: @"blurk"];
[theString retain];
[theString release];
Build and run this code, and you'll find that it doesn't crash. But there is a problem - go to the terminal and type:
ps -ax | grep "OutLauncher"
You should get back something that looks like:
1833 ?? Ss 0:00.04 /Users/drjay/OutLauncher/build/OutLauncher
1836 std UV+ 0:00.11 grep OutLauncher
The first number is our program's process ID - 1833 in this case. Use the process ID to check your program for leaks by typing "leaks 1833" (using your ID, not 1833). You'll find that your program is leaking badly, as leaks will return a list of leaked objects that will all look like:
Leak: 0x00055d50 size=30 instance of 'NSCFString'
0xa0130d40 0x000107f0 0x00055d40 0x00000005
0x00055590 0x00000000 0x00000000
Not surprisingly, it's leaking a string (that's all we used, after all) - noted as NSString's underlying representation, a Core Foundation string, CFString. What went wrong? Looking at the code, we both allocated the string and sent it a retain message, each of which retain the string. We only sent a single release message, though, which means that the string is retained even after we re-allocate it the next time through the loop. If we simply get rid of the retain message, this code will no longer leak.
Now that we've got some idea of what can go wrong with memory, you would think it would be time to start putting that knowledge to use. Before we do, however, we're going to take a bit of a detour tomorrow and look into RunLoops and executing code at regular time intervals.
If you have any questions or comments about this article, feel free to e-mail me at john_timmer@osxfaq.com