Don’t use non mutable objects to understand leak detection

This is a replay of a post I recently submitted to iPhoneSDK to explain why using non-mutable objects like NSString to learn leak detection is a bad idea.

The original post by Robert Scott complained that this code wasn’t showing Leaks in instruments even though it should be leaking a NSString with every pass.


Robert wrote:
So I guess there must be
something fundamental I don’t understand about leaks because I don’t
see why your examples cause leaks and this code does not:

  static int k;
  NSString *leakyString = [[NSString alloc]  initWithFormat:@"%1d", ++k] ;
  myLabel.text = leakyString;

The answer:

Because they aren’t leaks. If you try to use NSString to learn about
Objective-C memory management you will get burned over and over again,
because it uses tons of behind the scene tricks. NSString is not a
good place to learn to leak detection.

You are not the only one with references to those objects. This is
going to be long and involved, so get a cup of your favorite beverage,
download the example code and cuddle up with Xcode:

First off make sure you are using Xcode 3.1.2 for this demo, with the simulator set for 2.2.1. Effects may vary by platform and by version.

- (IBAction)pushMe:(id)sender;
{
  static int k;
  NSString *leakyString = [[NSString alloc]  initWithFormat:@"%1d", ++k] ;
//	myLabel.text = leakyString;
  NSLog(@"%@ %p %p",leakyString,leakyString,myLabel.text);
}

This code adds the printing of the pointers to leakyString and
myLabel.text For now the assignment to myLabel.text is commented
out. Run the code and watch the output of the debugger console. You
should get something like this:

2009-04-22 22:17:11.443 LeakSampler[27569:20b] 1 0xa03ca9f0 0x508540
2009-04-22 22:17:11.583 LeakSampler[27569:20b] 2 0xa03c3640 0x508540
2009-04-22 22:17:11.732 LeakSampler[27569:20b] 3 0xa03c9820 0x508540
2009-04-22 22:17:11.873 LeakSampler[27569:20b] 4 0xa03c8e20 0x508540
2009-04-22 22:17:12.013 LeakSampler[27569:20b] 5 0xa03c9dc0 0x508540
2009-04-22 22:17:12.153 LeakSampler[27569:20b] 6 0xa03ca520 0x508540
2009-04-22 22:17:12.294 LeakSampler[27569:20b] 7 0xa03c92a0 0x508540

Keep pushing until you get to:

2009-04-22 22:17:29.193 LeakSampler[27569:20b] 48 0xa03c9cd0 0x508540
2009-04-22 22:17:29.357 LeakSampler[27569:20b] 49 0x53e040 0x508540
2009-04-22 22:17:29.505 LeakSampler[27569:20b] 50 0xa03c7100 0x508540
2009-04-22 22:17:29.662 LeakSampler[27569:20b] 51 0xa03c6120 0x508540
2009-04-22 22:17:29.810 LeakSampler[27569:20b] 52 0xa03c4c30 0x508540
2009-04-22 22:17:29.958 LeakSampler[27569:20b] 53 0x5145d0 0x508540
2009-04-22 22:17:30.107 LeakSampler[27569:20b] 54 0xa03c99a0 0x508540

Note that for run 49 and 53 the results were different. They are in a
much lower section of memory.

Now use “Start with Performance Tool -> Leaks” and run the app.
Repeat all the way up to 54, (You will have to use Console.app to
watch the progress)

You should notice a few things. The pointers for 1..54, except for
for 49 and 53 were the EXACT same on this run. The only items that
show up as leaks are the entries for 49 and 53, which have the lower
memory addresses.

4/22/09 10:19:35 PM LeakSampler[27582] 1 0xa03ca9f0 0x508570
4/22/09 10:19:36 PM LeakSampler[27582] 2 0xa03c3640 0x508570
4/22/09 10:19:36 PM LeakSampler[27582] 3 0xa03c9820 0x508570
4/22/09 10:19:36 PM LeakSampler[27582] 4 0xa03c8e20 0x508570
4/22/09 10:19:36 PM LeakSampler[27582] 5 0xa03c9dc0 0x508570

You might be tempted at this point to think Leaks doesn’t detect leaks
with high memory addresses. You would be wrong. Play some more.

Remove the comment for setting the label, and start again without
Instruments.

- (IBAction)pushMe:(id)sender;
{
  static int k;
  NSString *leakyString = [[NSString alloc]  initWithFormat:@"%1d", ++k] ;
  myLabel.text = leakyString;
  NSLog(@"%@ %p %p",leakyString,leakyString,myLabel.text);
}

Note the first 5 lines of output.

2009-04-22 22:24:53.204 LeakSampler[27717:20b] 1 0xa03ca9f0 0xa03ca9f0
2009-04-22 22:24:53.939 LeakSampler[27717:20b] 2 0xa03c3640 0xa03c3640
2009-04-22 22:24:54.431 LeakSampler[27717:20b] 3 0xa03c9820 0xa03c9820
2009-04-22 22:24:54.790 LeakSampler[27717:20b] 4 0xa03c8e20 0xa03c8e20
2009-04-22 22:24:55.087 LeakSampler[27717:20b] 5 0xa03c9dc0 0xa03c9dc0

myLabel.text should be getting a copy of the string:

@property(nonatomic,copy)   NSString *text; //  default is nil

Yet the pointer for both leakyString and myLabel.text are exactly the
same. This is because NSString is non-mutable and copy of a non-
mutable object can simply be replaced with a reference to the exact
same object to reduce memory consumption.

Now a few more tests. Instead of NSStrings, create NSMutableStrings

2009-04-22 22:27:06.118 LeakSampler[27739:20b] 1 0x50c450 0xa03ca9f0
2009-04-22 22:27:06.431 LeakSampler[27739:20b] 2 0x53e7b0 0xa03c3640
2009-04-22 22:27:06.508 LeakSampler[27739:20b] 3 0x518200 0xa03c9820
2009-04-22 22:27:06.898 LeakSampler[27739:20b] 4 0x526210 0xa03c8e20
2009-04-22 22:27:07.267 LeakSampler[27739:20b] 5 0x53ef00 0xa03c9dc0

Note that the mutable strings are in low memory, yet the copies have
the same addresses as the very first run. The addresses for the
mutable strings will be different on each run as various other parts
of the system allocate and release memory.

Now for the fun part. Set a break point on the line that reads:

  static int k;

And fire up the program in GDB. Press the button one time and at the breakpoint, before your call to initWithFormat:, print the objects at the first five memory addresses that keep showing
up:

(gdb) po 0xa03ca9f0
1
(gdb) po 0xa03c3640
2
(gdb) po 0xa03c9820
3
(gdb) po 0xa03c8e20
4
(gdb) po 0xa03c9dc0
5

The NSString objects that you thought were all yours, that you loving
created with a clever call to initWithFormat:, turned out to just be
filthy dirty replicants of NSStrings generated before your code even
ran.

For fun, you can find some more strings that are already created:

(gdb) x/20x 0xa03c3640
0xa03c3640:	0xa03b84a0	0x000007c8	0x93c78a10	0x00000001
0xa03c3650:	0xa03b84a0	0x000007c8	0x93c80194	0x00000014
0xa03c3660:	0xa03b84a0	0x000007c8	0x93c801ac	0x00000002
0xa03c3670:	0xa03b84a0	0x000007c8	0x93c801b0	0x00000011
0xa03c3680:	0xa03b84a0	0x000007c8	0x93c801c4	0x00000001

Looks like tightly packed objects in a lookup table:

(gdb) po 0xa03b84a0
NSCFString

Yup, a bunch of NSCFString objects. A quick examination shows this
portion of the table to be:

(gdb) po 0xa03c3650
HelveticaNeue-Italic
(gdb) po 0xa03c3660
88
(gdb) po 0xa03c3670
orange_middle.png
(gdb) po 0xa03c3680
B

If you add this line of code what do you think you get?

NSLog(@"%p",[[NSString alloc] initWithFormat:@"%@",@"orange_middle.png"]);
2009-04-22 22:38:05.504 LeakSampler[27848:20b] 0xa03c3670

In Summary, I Repeat: If you try to use NSString or other non-mutable objects to learn about
Objective-C memory management you will get burned over and over again,
because it uses tons of behind the scene tricks. NSString is not a
good place to learn to leak detection.

As for why 49 and 53 aren’t in the table? Well I think that is blindingly obvious once you think about it.

This entry was posted in Debugging. Bookmark the permalink. Follow any comments here with the RSS feed for this post. Both comments and trackbacks are currently closed.
  • iChat Status