1

I've written the following code:

NSString *string = [[NSString alloc] initWithFormat:@"test"];
[string release];

NSLog(@"string lenght = %d", [string length]);
//Why I don't get EXC_BAD_ACCESS at this point?

I should, it should be released. The retainCount should be 0 after last release, so why is it not?

P.S. I am using latest XCode.

Update:

NSString *string = [[NSString alloc] initWithFormat:@"test"];

NSLog(@"retainCount before = %d", [string retainCount]);// => 1

[string release];

NSLog(@"retainCount after = %d", [string retainCount]);// => 1 Why!?
Sergey Zenchenko
  • 854
  • 1
  • 12
  • 31

4 Answers4

4

In this case, the frameworks are likely returning the literal @"test" from NSString *string = [[NSString alloc] initWithFormat:@"test"];. That is, it determines the literal may be reused, and reuses it in this context. After all, the input matches the output.

However, you should not rely on these internal optimizations in your programs -- just stick with the reference counting rules and well-defined behavior.

Update

David's comment caused me to look into this. On the system I tested, NSString *string = [[NSString alloc] initWithFormat:@"test"]; returns a new object. Your program messages an object which should have been released, and is not eligible for the immortal string status.

Your program still falls into undefined territory, and happens to appear to give the correct results in some cases only as an artifact of implementation details -- or just purely coincidence. As David pointed out, adding 'stuff' between the release and the log can cause string to really be destroyed and potentially reused. If you really want to know why this all works, you could read the objc runtime sources or crawl through the runtime's assembly as it executes. Some of it may have an explanation (runtime implementation details), and some of it is purely coincidence.

justin
  • 104,054
  • 14
  • 179
  • 226
  • 1
    I'm quite certain that this isn't the case... the object is really gone but the memory is still untouched so the pointer goes to a valid object in memory and that is why this works. Putting other calls in between will break it. – David Rönnqvist Jun 15 '12 at 20:13
  • @DavidRönnqvist thanks - you're right. `-[NSString initWithFormat:]` did not make this optimization on the system I tested with. – justin Jun 15 '12 at 20:39
3

Doing things to a released object is an undefined behavior. Meaning - sometimes you get away with it, sometimes it crashes, sometimes it crashes a minute later in a completely different spot, sometimes a variable ten files away gets mysteriously modified.

To catch those issues, use the NSZombie technique. Look it up. That, and some coding discipline.

This time, you got away because the freed up memory hasn't been overwritten by anything yet. The memory that string points at still contains the bytes of a string object with the right length. Some time later, something else will be there, or the memory address won't be valid anymore. And there's no telling when this happens.

Sending messages to nil objects is, however, legitimate. That's a defined behavior in Objective C, in fact - nothing happens, 0 or nil is returned.

Seva Alekseyev
  • 59,826
  • 25
  • 160
  • 281
  • This doesn't answer the OP's question. See my and Justin's answer. –  Jun 15 '12 at 19:49
  • @H2CO3: You're guessing the internals of Cocoa, with no proof and no docs to back you up. My answer to the OP's question is "this time you got away with it, but you won't always". – Seva Alekseyev Jun 15 '12 at 19:53
  • But he looks for an explanation why this happens and not for good programming practices. Btw, did you notice my sentence beginning with `To prove this, ...`? –  Jun 15 '12 at 19:54
  • Did you *run* the proof on a device? Did your theory hold? And - more importantly - can you be sure it will hold in every version of iOS? By the way, the OP used [initWithFormat], not [initWithString]. – Seva Alekseyev Jun 15 '12 at 19:56
  • You're right in one thing: it was surely `initWithFormat:`. Fixed. –  Jun 15 '12 at 20:00
  • 1
    It works by coincidence. This is the correct answer. While one might guess at implementation detail, it is irrelevant (though possibly interesting). – bbum Jun 15 '12 at 20:03
3

Update:

Ok. I'm tired and didn't read your question carefully enough.

The reason you are not crashing is pure luck. At first I though that you were using initWithString: in which case all the answers (including my original one (below)) about string literals would be valid.


What I mean by "pure luck"

The reason this works is just that the object is released but your pointer still points to where it used to be and the memory is not overwritten before you read it again. So when you access the variable you read from the untouched memory which means that you get a valid object back. Doing the above is VERY dangerous and will eventually cause a crash in the future!

If you start creating more object in between the release and the log then there is a chance that one of them will use the same memory as your string had and then you would crash when trying to read the old memory.

It is even so fragile that calling log twice in a row will cause a crash.


Original answer:

String literals never get released!

Take a look at my answer for this question for a description of why this is.

This answer also has a good explanation.

Community
  • 1
  • 1
David Rönnqvist
  • 56,267
  • 18
  • 167
  • 205
2

One possible explanation: You're superfluously dynamically allocating a string instead of just using the constant. Probably Cocoa already knows that's just a waste of memory (if you're not creating a mutable string), so it maybe releases the allocated object and returns the constant string instead. And on a constant string, release and retain have no effect.

To prove this, it's worth comparing the returned pointer to the constant string itself:

int main()
{
    NSString *s = @"Hello World!";
    NSString *t = [[NSString alloc] initWithFormat:s];

    if (s == t)
        NSLog(@"Strings are the same");
    else
        NSLog(@"Not the same; another instance was allocated");

    return 0;
}