TweetFollow Us on Twitter

Debugging Retain Count Bugs

Volume Number: 21 (2005)
Issue Number: 4
Column Tag: Programming

Tips From Big Nerd Ranch

by Aaron Hillegass, Chris Campbell, and Marquis Logan

Debugging Retain Count Bugs

At the Big Nerd Ranch, we teach the world's greatest Cocoa programming course (and the greatest Python, PHP, Perl, and PostgreSQL classes). One of the things that makes the course great is the alumni mailing list. We thought it would be a great service to the larger developer community if we shared some of the tips that have appeared on the list. So each month, we are going to cull through the discussion and publish a few pieces of useful wisdom. This month, we are talking about how to debug errors in the use of retain and release.

Hunting down bugs in your use of retain and release can be very difficult. Before delving into all the ways to find them, let's quickly review the lifetime of an object on the heap. The object is allocated (through the +allocWithZone: method which calls the malloc_zone_calloc() function), it is used, and then it is deallocated (through the -dealloc method which calls the malloc_zone_free() function).

Complicating matters is the retain count. A newly allocated object has a retain count of 1. When an object is retained, the retain count is incremented. When it is released, the retain count is decremented. When the retain count goes to zero, the object is deallocated.

This is complicated even further by the autorelease pool. When an object is sent the message autorelease, it adds itself to the current autorelease pool. When the autorelease pool is deallocated, it send the message release to every object in the pool. In a Cocoa application, the autorelease pool is deallocated after each event is handled.

Use release when possible

Here are two similar erroneous methods:

- (void)bad
{
    NSArray *array = [[NSArray alloc] init];
    [array release];
    [array release];
}

- (void)worse
{
    NSArray *array = [[NSArray alloc] init];
    [array release];
    [array autorelease];
}

The first method is run inside a debugger, it will stop on the erroneous line with a BAD_ACCESS error. The second method will cause a BAD_ACCESS only when the autorelease pool is deallocated. In a large program, it may be difficult to back track to where you autoreleased that object. Using release, besides being more efficient, will make your code easier to debug.

Note that this does not mean that you should break conventions: when creating an object to be vended out, it should be autoreleased:

- (NSNumber *)currentPrice
{
    NSNumber *p = [[NSNumber alloc] initWithInt:(whatTheMarketWillBear - 1)];
    [p autorelease];
    return p;
}

Set Pointers to nil after Objects are Released

To make it impossible to send messages to released objects, set the pointer to nil after the object is released:

- (void)fireThem
{
   [employees release];
   employees = nil;
}

Using Zombies

This code (which uses an object after it has been deallocated) often runs without crashing or throwing exceptions:

- (IBAction)stupidAction:(id)sender
{
    NSMutableString *string = [[nameField stringValue] mutableCopy];
    [string release];
    NSMutableString *anotherString = [NSMutableString stringWithFormat:@"I'm new!"];
    NSLog(@"string = %@", string);
}

The new string object is sometimes created in the same place in memory that the old one had occupied. So, when you run this you sometimes see:

string = I'm new!

On the other hand, sometimes the new string isn't created in the same location and the application crashes with a BAD_ACCESS. As you can imagine, hunting down a bug may or may not rear its ugly head can be maddening.

To debug this code, instead of freeing the memory for reuse, turn the objects into zombies. When you try to use a zombie, Cocoa will throw an exception. Thus, you can consistently reproduce this bug. The trick, then, is to tell Foundation and CoreFoundation, that you want to zombify your objects instead of freeing them.

First, you need to use the debug version of the CoreFoundation framework. To do this, select your executable in Xcode, and in the inspector under the General tab, choose "Use the debug suffix when loading frameworks."

Then, under the Arguments tab, set the environment variable NSZombieEnabled to YES and CFZombieLevel to 5:

Now when you send a message to a released object you will get a nice exception like this:

*** Selector 'respondsToSelector:' sent to dealloced instance 0x328840 of class NSMutableString.

(Remember to disable zombies once the bug is found -- you want the objects to be freed, not zombified in a deployed app.)

Putting a breakpoint on exceptions

Now that you have an exception that is thrown consistently, you will need to create a breakpoint in the debugger where the exception gets raised. Before dealing with the debugger, let's review how exceptions get thrown.

The Cocoa libraries check certain conditions, and if the conditions are not met, they raise exceptions. For example, if you ask for the second item in an array that has only one item, an exception will be thrown. Throwing an exception looks like this:

if (x == 13) {
    NSException *badness = [NSException exceptionWithName:@"BadLuckException"
                                                    reason:@"13 is unlucky"
                                                    userInfo:nil];
    [badness raise];
}

Thus, to stop the debugger as soon as an exception is raised, you can add the breakpoint for -[NSException raise] using the breakpoint panel.

Gradually, Cocoa programmers are moving to the new @throw/@catch/@finally form of exceptions stolen from C++ and Java. In this form, you will throw exceptions like this:

    NSException *badness = [NSException exceptionWithName:@"BadLuckException"
                                                   reason:@"13 is unlucky"
                                                   userInfo:nil];
    
    @throw badness;

(To use new-style exceptions, you must pass the -fobjc-exceptions flag to the compiler.)

To detect when an exception is being thrown from within the debugger, you will want to create a breakpoint on objc_exception_throw().

You will want these breakpoints every time you run the debugger. To automatically add these breakpoints anytime you use gdb, create a .gdbinit file in your home directory and include these lines:

fb -[NSException raise]
fb objc_exception_throw()

Those are the four tips for this week:

  • release instead of autorelease when possible
  • After releasing an object, set pointers to it to nil
  • Use zombies to hunt down retain/release problems
  • Add breakpoints to stop on exceptions

We hope they are useful to you, and we will be back next month with a few more.


Aaron Hillegass, Chris Campbell, and Marquis Logan

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Combo Quest (Games)
Combo Quest 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: Combo Quest is an epic, time tap role-playing adventure. In this unique masterpiece, you are a knight on a heroic quest to retrieve... | Read more »
Hero Emblems (Games)
Hero Emblems 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: ** 25% OFF for a limited time to celebrate the release ** ** Note for iPhone 6 user: If it doesn't run fullscreen on your device... | Read more »
Puzzle Blitz (Games)
Puzzle Blitz 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Puzzle Blitz is a frantic puzzle solving race against the clock! Solve as many puzzles as you can, before time runs out! You have... | Read more »
Sky Patrol (Games)
Sky Patrol 1.0.1 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0.1 (iTunes) Description: 'Strategic Twist On The Classic Shooter Genre' - Indie Game Mag... | Read more »
The Princess Bride - The Official Game...
The Princess Bride - The Official Game 1.1 Device: iOS Universal Category: Games Price: $3.99, Version: 1.1 (iTunes) Description: An epic game based on the beloved classic movie? Inconceivable! Play the world of The Princess Bride... | Read more »
Frozen Synapse (Games)
Frozen Synapse 1.0 Device: iOS iPhone Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: Frozen Synapse is a multi-award-winning tactical game. (Full cross-play with desktop and tablet versions) 9/10 Edge 9/10 Eurogamer... | Read more »
Space Marshals (Games)
Space Marshals 1.0.1 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.1 (iTunes) Description: ### IMPORTANT ### Please note that iPhone 4 is not supported. Space Marshals is a Sci-fi Wild West adventure taking place... | Read more »
Battle Slimes (Games)
Battle Slimes 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: BATTLE SLIMES is a fun local multiplayer game. Control speedy & bouncy slime blobs as you compete with friends and family.... | Read more »
Spectrum - 3D Avenue (Games)
Spectrum - 3D Avenue 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: "Spectrum is a pretty cool take on twitchy/reaction-based gameplay with enough complexity and style to stand out from the... | Read more »
Drop Wizard (Games)
Drop Wizard 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Bring back the joy of arcade games! Drop Wizard is an action arcade game where you play as Teo, a wizard on a quest to save his... | Read more »

Price Scanner via MacPrices.net

Apple’s M4 Mac minis on sale for record-low p...
B&H Photo has M4 and M4 Pro Mac minis in stock and on sale right now for up to $150 off Apple’s MSRP, each including free 1-2 day shipping to most US addresses. Prices start at only $469: – M4... Read more
Deal Alert! Mac Studio with M4 Max CPU on sal...
B&H Photo has the standard-configuration Mac Studio model with Apple’s M4 Max CPU in stock today and on sale for $300 off MSRP, now $1699 (10-Core CPU and 32GB RAM/512GB SSD). B&H also... Read more

Jobs Board

All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.