views:

243

answers:

4
#import <Foundation/Foundation.h>

int main (int argc, char const *argv[])
{
    SampClass *obj=[[SampClass alloc] init];
    [obj release];
    NSLog(@"%i", [obj retainCount]);
    return 0;
}

Why does this give retainCount of 1, when it should be 0

A: 

Because the object has been deallocated before your NSLog call.

I assume that release is implemented something like the following:

if(retainCount==1) [self dealloc];
else retainCount--;
and you are accessing the deallocated object illegally.

Update: Found these questions whose answers should enlighten you further: here and here.

yngvedh
If `release` was implemented that way, `obj` could be deallocated more than once.
mouviciel
@mouviciel only if you send too many release messages to it, which you aren't supposed to (not considering multithreading issues). I was just trying to explain why SVA's example prints 1. But since the object is deallocated, it is not really possible to assume anything about its contents.
yngvedh
A: 

The object is being dealloced but once the object goes to retain count 0 any method calls are likely to return stale values.

If you compile and run on 32 bit, you will get an error (message retainCount sent to freed object=0x...).

I'm guessing the 64bit runtime (default compile option on Leopard) does not collect objects as aggressively as the 32 bit runtime so your call to retainCount does not cause an error.

To check that you object is indeed dealloced, implement dealloc for your SampClass:

- (void) dealloc
{
    NSLog(@"Dealloc");
    [super dealloc];
}

Later edit: As I suspected, the difference in behavior between 32 and 64 bit when calling a method on a released object is from the runtime and not undefined behavior.

In 32 bit, once a class is freed it's isa pointer is switched to a special class that allows intercepting messages to freed objects. This does not happen in 64 bit. Relevant source is objc-class.m:

#if !__OBJC2__ 
    // only clobber isa for non-gc
    anObject->isa = _objc_getFreedObjectClass (); 
#endif
diciu
That makes no sense. There's no question of "aggressive collection" in reference-counted code-if you release, and it's the final balanced release, the object gets dealloced. Not later, now.
Graham Lee
@Graham - I can't explain the fact that, in 64bit, dealloc on the object is called, and yet I can still call retainCount on it. My only guess is that *free* is not called on the object but rather the object is added to some "free later" pool.
diciu
What does `free` do? Does it guarantee to change the `isa` pointer of a deallocated object? Probably not; if you try to message a deallocated object, the runtime could see any old garbage _including the last-good state of the object_ in that memory location. That doesn't make it right, likely, or worth depending on.
Graham Lee
@Graham - your comment makes perfect sense, yet it does not explain the difference in behavior in between 32 and 64 bit. In 32bit __objc_error error is called, on 64 bit it is not.
diciu
Does undefined behaviour need to have its behaviour explained? :)
Graham Lee
I don't think this is a matter of dangling pointers but rather a difference in the runtime implementation. *_freedHandler* exists in 32 bit but not in 64 -> the ObjC runtime implementations are not the same.I think the behavior is not undefined, but undocumented.
diciu
The behavior of messaging an already deallocated object is undefined.
bbum
@diciu: There are quite a few differences between the 32-bit and 64-bit runtimes. It would not surprise me if undefined behaviour in the 32-bit runtime was different to the undefined behaviour in the 64-bit runtime. Compiler optimisations would also play a role, if the compiler chose to reuse stack space, your object pointer variable may be overwritten with something that is not a valid pointer to an object.
dreamlax
@bbum - At least in 10.6.2, there's a #ifdef on !__OBJC2__ in the runtime. If it's true, messages to freed objects are trapped.
diciu
+1  A: 

There's no question of what retainCount should be in your code, you shouldn't be calling it. Relying on the result of messaging a deallocated object is not a good idea. Here's what happens when I copy your code, and run it with one of the (numerous) retain-count-debugging features of the frameworks:

heimdall:~ leeg$ pbpaste | sed s/SampClass/NSObject/g > fubar.m
heimdall:~ leeg$ cc -o fubar -framework Foundation fubar.m 
heimdall:~ leeg$ NSZombieEnabled=YES ./fubar
2010-01-07 13:40:10.477 fubar[871:903] *** -[NSObject retainCount]: message sent to deallocated instance 0x10010d8f0
Trace/BPT trap
Graham Lee
+9  A: 

Do not call retainCount.

Not even in debugging code. And especially not when you are trying to learn how Cocoa's memory management works.

The absolute retain count of an object is not something under your control. Often, the value will be quite unexpected. There may be any number of caches, static allocations (like constant NSStrings), or other internal implementation details within frameworks that make an object's retain count other than what you expect.

The retain count of objects should be thought of entirely in terms of deltas. If you cause the retain count to increase, you must decrease it somewhere if you want the object to be deallocated. Period. End of story.

Trying to think of retain counts in absolute terms will just lead to confusion and wasted hours.

The Cocoa Memory Management Guide explains this rather well.

bbum