• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Re: Use of Mac OS X 10.5 / Leopards Garbage Collection Considered Harmful
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

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


  • Prev by Date: Re: Animation in Cocoa too slow? [was: Animation with CALayer?]
  • Next by Date: Re: Memory(?) issue
  • Previous by thread: default expand outline view items
  • Next by thread: Determining if a url exists
  • Index(es):
    • Date
    • Thread