Re: Use of Mac OS X 10.5 / Leopards Garbage Collection Considered Harmful
Re: Use of Mac OS X 10.5 / Leopards Garbage Collection Considered Harmful
- Subject: Re: Use of Mac OS X 10.5 / Leopards Garbage Collection Considered Harmful
- From: John Engelhart <email@hidden>
- Date: Wed, 6 Feb 2008 20:06:24 -0500
On Feb 6, 2008, at 2:12 PM, Alastair Houghton wrote:
On 6 Feb 2008, at 17:52, Hamish Allan wrote:
On Feb 6, 2008 2:59 PM, John Engelhart <email@hidden>
wrote:
"I'll take 'Not relevant' for $200 and 'Misunderstands the
fundamentals' for the win, Alex."
Speaking of "not relevant" and "misunderstands the fundamentals":
1) UTF8String returns a non-__strong pointer.
__strong isn't a type qualifier, it's an attribute (in the sense of
the __attribute__ keyword). The distinction is perhaps a bit
subtle, especially as attributes can be attached to a typedef'd
type, but it's the reason that you can put __strong anywhere in a
variable declaration and it still has the same effect. It *isn't*
like const or volatile, and the ANSI C rules regarding type
qualifiers absolutely *do not* apply.
As I mentioned previously, having no formal grammar of ObjC 2.0 makes
this point debatable. Your interpretation that __strong is an
__attribute__ extension could certainly be true, and is a valid way of
looking at things. Without a grammar to guide us, I think both
interpretations are equally valid.
However, consider for a moment if it was a type attribute, and
followed type attribute rules, and how this would effect the examples
cited. Off the top of my head, I think treating it as a type
attribute would have prevent every single error I've pointed out.
Hypothetically, consider if UTF8String propagated the __strong type,
and the assignment of its pointer to the ivar 'const char *'.
The compiler would fail to compile the code, and generate an error.
Again, I'm pretty sure that every example posted would be caught by if
__strong was treated as a type attribute.
Since we have two valid ways to interpret the meaning of __strong, I
believe that the usefulness of catching these errors at compile time
argues strongly in favor of considering it a type attribute.
Furthermore I *think* (and this is from memory, based on some work I
did on GCC several years ago, so I might be wrong) that if you write
something like
void * __strong MyFunction(void);
you'll find that the __strong attribute is attached to the
*function* rather than to the type. In any case it's going to be
ignored because __strong only really affects variables, not types or
functions.
I hate this part of the language. Each qualifier has it's quirks, and
their application is non obvious, including exactly what they apply
to. The distinction between
const char * ptr; and
char * const ptr;
isn't terribly clear by just looking at it. Add to this
const char * const ptr;
Three, totally different things, and using const twice in this way
just seems like it would be a bug at first glance.
2) You fail to copy the data at that pointer.
3) You cry foul when that data disappears.
Well the bug is *either* ignoring the bit in the -UTF8String docs
where it says you should copy the string (though that does read like
it was only intended to talk about the non-GC case---I just filed <rdar://5727581
> asking for a clarification), or not using __strong on the variable
you're storing the result in.
Actually, I've thought of another example which addresses the use of
(or lack of) __strong unambiguously and still demonstrates the problem:
#import <Foundation/Foundation.h>
@interface GCTest : NSObject {
const char *title;
};
- (void)setTitle:(const char *)newTitle;
- (const char *)title;
@end
@implementation GCTest
- (void)setTitle:(const char *)newTitle
{
printf("Setting title. Old title: %p, new title %p = '%s'\n",
title, newTitle, newTitle);
title = newTitle;
}
- (const char *)title
{
return title;
}
@end
int main(int argc, char *argv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
GCTest *gcConstTitle = NULL, *gcUTF8Title = NULL;
void *ptr;
gcConstTitle = [[GCTest alloc] init];
gcUTF8Title = [[GCTest alloc] init];
[gcConstTitle setTitle:"Hello, world!"];
[gcUTF8Title setTitle:[[NSString stringWithUTF8String:"Hello, world
\xC2\xA1"] UTF8String]];
NSLog(@"Test: %@", @"hello");
[[NSGarbageCollector defaultCollector] collectExhaustively];
NSLog(@"GC test");
printf("gcConstTitle title: %p = '%s'\n", [gcConstTitle title],
[gcConstTitle title]);
printf("gcUTF8Title title: %p = '%s'\n", [gcUTF8Title title],
[gcUTF8Title title]);
[gcConstTitle setTitle:NULL]; // Must clear the pointer before
popping pool.
[gcUTF8Title setTitle:NULL];
[pool release];
return(0);
}
[johne@LAPTOP_10_5] /tmp% gcc -framework Foundation -fobjc-gc-only -o
gc -g gc.m
[johne@LAPTOP_10_5] /tmp% ./gc
Setting title. Old title: 0x0, new title 0x1ea4 = 'Hello, world!'
Setting title. Old title: 0x0, new title 0x1011860 = 'Hello, world¡'
2008-02-06 18:32:35.712 gc[18108:807] Test: hello
2008-02-06 18:32:35.798 gc[18108:807] GC test
gcConstTitle title: 0x1ea4 = 'Hello, world!'
gcUTF8Title title: 0x1011860 = 'Hello, world'
Setting title. Old title: 0x1ea4, new title 0x0 = '(null)'
Setting title. Old title: 0x1011860, new title 0x0 = '(null)'
Oddly, I had to add a second NSLog() in order to get some kind of
lossage, but I think it's fair to chalk this up to the semi-random
nature of allocations.
The above example is now perfectly legal by everyones definition of
how things were under retain/release, and I correctly clear the
pointer before it goes out of scope, and demonstrates that the GC
system can, and does, reclaim live data out from under you.
A garbage collection systems sine qua non is to free the programmer
from having to deal with the issues memory allocation.
Exactly. So stop getting your knickers in a twist about whether or
not
UTF8String actually returns memory from NSAllocateCollectable(), and
simply copy the result as required by the documentation for
UTF8String.
Indeed, I rather wish I hadn't mentioned NSAllocateCollectable(),
since I think it's only muddied the waters further.
I don't. Again, consider the hypothetical case where __strong is type
qualifier. During development of UTF8String, this would cause a
warning or error (implementation dependent, but I'd argue for error).
This would highlight the fact that the prototype is discarding a
critical qualifier. Assuming that the definition of UTF8String was
altered to include __strong, my attempt at assigning a __strong
qualified pointer to a non-strong pointer would instantly be flagged
and reported by the compiler.
Consider the case of handing a __strong pointer off to a function,
such as CFStringCreateWithCStringNoCopy(). If the prototype does not
have __strong for the buffer argument, my example of handing it an
NSAllocateCollectable pointer would again, instantly trigger a
compiler warning or error (I vote error considering the consequences).
It's hard to argue that this is not "The Right Thing" to be doing as
it would have mooted every single point I have raised, and caught all
of these errors at compile time before they could have become
problems. This would have also caught the problem in the example I
pasted above, at compile time, and alerted me that I just created a
problem.
Finally, consider the effects of the current behavior of silently
discarding __strong. As the example in this message shows, it's
surprisingly easy to create conditions which violate the conditions
required for proper GC behavior.
After four months of practical, hands on usage of Leopards GC system,
my experience is that this is happening far, far more frequently than
you might think. I have had to deal with endless problems under GC
which have all the tell tale signs of race conditions. Because what
I'm developing is dual mode, flipping over to retain/release works
flawlessly, even after intensive multithreaded concurrent extreme
stress testing. The retain/release side of thing never crashes, but
the GC side of things mostly doesn't crash.
I think the evidence I've put forth is pretty strong. I think the
example I give in this message addresses everyones concerns regarding
the use of __strong. It's pretty clear that I don't need to use
__strong for the pointer, and that under retain/release methodology,
it is incapable of freeing the UTF8String buffer while its still in
use. It's certainly open to debate how frequently this happens in
practice. I have shown that it is possible. In my opinion, after
four months of use, this is happening much more frequently than you
might think at first approximation. And just like race conditions, it
gets worse the harder you push, which is to be expected. It's pretty
much a ticking time bomb, and everything seems to work fine when
you're doing development, but then starts failing mysteriously out in
the field._______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden