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: Ben Trumbull <email@hidden>
- Date: Wed, 6 Feb 2008 19:52:43 -0800
John,
You would undoubtedly receive greater participation in this thread
and your concerns if they were expressed with less vitriol.
Read the above, "object" is synonymous for "struct". The "layout" of
an object is identical to the "layout" of a struct.
Objects are not structs. The compiler never treated them
identically, as can be witnessed by the insanity of type encoding
strings. (See Jim Correia's recent thread).
As you've noted, objects behave a lot like structs under many
circumstances. I too find this incredibly useful, and have leveraged
it for various (and nefarious) reasons.
An analogy is light. You can argue it's a wave all you want, and it
does have wave-like properties, but it's not a wave. Neither is it
strictly a particle. In lay terms, it behaves like both and, as far
as it matters to me, light *is* both a wave and a particle.
Objects are structs, but they're also more. They are not synonymous.
The "layout" of an object is not identical to a struct. Each
instance of a class (object), in the 32 bit runtime, can be cast to
and from a struct representation. But the layout information that
makes an object not a struct is in the Class. This layout even
includes information for GC regarding which ivars are strong, weak,
or neither. This layout information is significantly more detailed
than the layout information for a plain struct and always has been.
At runtime, structs lack any meaningful metadata about the number,
type, and size of their elements.
Your example demonstrates this weakness. The best you can do is
pointer aliasing, not actually making a new struct with a new Class.
You could use the Objective-C runtime to make a new class, but then
you've just created the layout information that makes objects
different. Mallocing a buffer, and forcing an isa into it is
promoting a void* into an id. And that is limited by actually
creating a buffer of the correct size. You clearly can't cast just
any void*, even with an isa, into an id. And we haven't even
addressed the issue that classes can reasonably expect to receive
+alloc instead of being coerced from malloc() ...
You cannot message a void* without type casting. The compiler won't
let you. The compiler treats objects differently. Further, there is
no way for you to accept an arbitrary void* parameter and prove at
runtime it is in fact capable of dispatching messages (i.e. has an
isa at offset 0). Yet I always know I can message an id.
For any arbitrary id, I can invoke -class without crashing. This is
not true for void*, and it's what drives me crazy about Cocoa APIs
that use void* (like KVO's -observe... method) Clearly id != void*,
no matter how convenient the known layout of a 32 bit object is for
various optimizations and hacks. Or put another way, even in the old
32 bit runtime, id is a subset of void*
The Objective-C 2 APIs accentuate this. The Class has become more
opaque, @defs is deprecated, direct pointer manipulation of the Class
and method lists is deprecated, etc.
Regardless of the degree to which objects behaved as structs in the
past, no matter how right you were, it's clear that Objective-C is
moving rapidly towards a runtime in which objects are not at all
structs. The 64 bit runtime has non-fragile ivars and no @defs
support at all.
For example:
@interface GCTest : NSObject {
__strong void *ptr;
}
@implementation GCTest
- (void)setPtr:(void *)newPtr
{
ptr = newPtr;
}
__strong does not modify any layout information
Wrong. That layout information is stored in the GCTest class. The
Class knows which ivars are strong or weak; objects or not.
but it is a reasonable assumption that "__strong" is in the same group of
type qualifiers as "const" and "volatile". This makes __strong
subject to the same ANSI-C rules governing the use of type qualifiers,
including promotion and assignment rules.
This assumption is wrong. It's a type attribute, and as best I can
tell, the similarity to a proper type qualifier is an implementation
detail. It would be perfectly reasonable to ask for better
documentation on this point.
Since the pointer that UTF8String returns is provably from
NSAllocateCollectable, this prototype has /DISCARDED/ the type
qualifier of __strong.
Which is irrelevant. What matters is where you put it. By omitting
__strong from the ivar declaration in GCTest you have instructed the
system to not follow 'ptr'. When GC finds a GCTest object, it skips
over ptr for scanning of live memory blocks.
QED, my use of the UTF8String pointer is bug free and legal by ANSI-C
rules.
We're not in ANSI C. The GCTest class you originally provided is wrong.
Because of the design of Leopards GC system, it is the PROGRAMMERS
responsibility to INFER when a pointer should be __strong qualified.
You keep saying this, but the error is in the construction of the
GCTest class and has nothing whatsoever to do with your usage of
-UTF8String. You don't need to infer anything about the pointer,
only how the result is stored by your code.
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.
This is completely false. The pointer returned by -UTFString is
effectively in the autorelease pool. If the NSString is
autoreleased, and you pop the autorelease pool where your example
currently invokes a full GC operation, your code will crash just as
badly.
Test with MallocScribble for best results.
- (NSString *)someMethod
{
NSUInteger finalStringLength = 1024; // Example only
NSString *copySting = NULL;
char * __strong restrict copyBuffer = NULL;
copyBuffer = NSAllocateCollectable(finalStringLength, 0);
/* Since this is just an example, the part that fills contents of
copyBuffer with text are omitted */
copyString =
NSMakeCollectable
((id)CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, copyBuffer,
kCFStringEncodingUTF8, kCFAllocatorNull));
/* kCFAllocatorNull = This allocator is useful as the
bytesDeallocator in CFData or contentsDeallocator in CFString where
the memory should not be freed. So.. Don't call free() on our
NSAllocateCollectable buffer, which is an error. */
return(copyString);
}
You see where the bug is, right?
Actually, this works as documented:
"it is your responsibility to assure the buffer does not go away
during the lifetime of the string"
Unfortunately, if you use kCFAllocatorDefault instead, it displays
the non-intuitive behavior of not working. Deallocation by the CF
allocator needs to be balanced by allocation from the CF allocator,
not from NSAllocateCollectable(). That should be better documented.
How about this:
- (id *)copyOfObjectArray:(id *)originalObjects length:
(NSUInteger)length
{
id *newObjectArray = NULL;
newObjectArray = NSAllocateCollectable(sizeof(id) * length,
NSScannedOption);
memcpy(newObjectArray, originalObjects, sizeof(id) * length);
return(newObjectArray);
}
This does not work. Pushing GC'd objects through memcpy, a system
call that can't know anything about Objective-C Garbage Collection,
seems unwise.
Nonetheless, that also should be better documented, and a bug report
for a public GC compatible memory copy API would be good.
If you stick to working with Objective-C APIs, or follow the
guidelines for Core Foundation types and use CF APIs, life is much
simpler. This seems to work well for many developers.
Trying to mix scanned and unscanned memory, and work with scanned
memory via non-GC libraries, is possible but very difficult.
Retrofitting large projects using old patterns can be very
frustrating. It's my (personal) understanding that the focus was on
new projects and code written from the ground up with GC, and this
was articulated at WWDC.
I realize this is not the GC system that you would have implemented,
however the mixed open/closed nature of the system is documented, and
has valid design objectives. There is a decent overview of the
architecture and design inflection points in the programming guide.
I can understand after 4-5 months of working with a technology that
does not behave the way you expect, and does not solve the your
problems, that you are extremely frustrated. However, this does not
mean that the Leopard GC system is bad for everyone. Finding one
small bug in CFStringCreateWithCStringNoCopy is a far far cry from
fatally foobar.
Frankly, you're predicament evokes one of my favorite quotes
(attribution forgotten):
"I spent six months in the lab to save myself six hours in the library"
I sincerely hope you and others will come to the list, or Apple
Developer Relations, or any number of other excellent Cocoa and OSX
communities long before 4 months of frustration builds to this level.
If all those resources fail you, imo, this kind of issue would be a
wise use of a DTS ticket. At least to understand how this technology
will or won't help you. Online (free) ADC members can purchase a
support ticket for $195. A lot cheaper than 4 months of your time,
even if you don't like the answer. For more information:
<http://developer.apple.com/faq/techsupport.html>
--
-Ben
_______________________________________________
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