Mark A Vitale

Objective-C's Tagged Pointers and Localized Over-releases

2021-04-13 by Mark A Vitale

Disclaimer: This particular class of problem can be more easily solved by using ARC. The code I was working in did not use ARC at the time for a variety of reasons, so manual memory management was necessary.

The Crash

A few years back I was investigating a crash in some Objective-C code that was pretty easy to figure through code inspection. This code didn’t use ARC and required manual memory management. At some point a localized string was assigned to an instance variable and failed to copy or retain it. I did, however, remember to release the instance variable in dealloc, resulting in a pretty obvious crash.

- (void)setString:(NSString *)string {
    // ...
    _string = string; // Needs a copy or retain
    // ...

- (void)dealloc {
    // ...
    [_string release];
    // ...

The Inconsistency

Despite having found the root cause of the crash, I couldn’t reconcile in my head why this crash would only occur in certain languages. In particular we were not seeing it in English, French, or Spanish, (in)conveniently the languages I chose to verify that the feature was working properly in different localizations.

The Explanation

What was happening? Apple’s too clever Tagged Pointers were in play here. If the data being pointed to by a pointer is actually smaller than the size of the pointer itself, the value of the object can be packed into the pointer itself. This happens transparently for a few specific object types1.

Apple has some tricks to determine whether a pointer is a “real” pointer or a tagged pointer. The details of that are too technical for this particular post, but Mike Ash has an awesome blog post you can look at if you’re more interested on the technical implementation details 2.

Because the value of the object is stored in the pointer value themselves, retaining, copying, and releasing them are no-ops. If you have the pointer, you have the value. So over-releasing, leaking, and related issues don’t really exist with a tagged pointer (and the Zombies tool won’t find memory issues with tagged pointers either). Another important detail is that tagged pointers are completely transparent to the developer. When creating an object of one of those types listed above, if it fits, Apple will create a tagged pointer, and if it doesn’t, it won’t. So you always have to manage memory correctly and can’t bank on it being a tagged pointer and ignoring memory concerns.

So in our crash case, purely by chance, the English, French, and Spanish strings were all small enough to fit into an Tagged Pointer and thus weren’t crashing on an over-release because release is a no-op on tagged pointers. In other languages where by chance the string required a true pointer to some memory on the heap, the missing copy/retain did matter and our crashes were consistent.

Thanks for reading, I hope you found this interesting!

For more reference on tagged pointer strings, check out Mike Ash’s super awesome post here.

  1. It’s not just NSString. Here is a list of object types that can be implemented as a tagged pointer:

    OBJC_TAG_NSAtom            = 0,
    OBJC_TAG_1                 = 1,
    OBJC_TAG_NSString          = 2,
    OBJC_TAG_NSNumber          = 3,
    OBJC_TAG_NSIndexPath       = 4,
    OBJC_TAG_NSManagedObjectID = 5,
    OBJC_TAG_NSDate            = 6,
    OBJC_TAG_7                 = 7

    From objc-internal.h, discovered via a Stack Overflow post

  2. Friday Q&A 2012-07-27: Let’s Build Tagged Pointers - by Mike Ash 

If you like this, follow the RSS feed to be notified of new posts. Looking for an RSS reader? On macOS and iOS, I like NetNewsWire.

Found this useful or want to support more articles in the future? You can buy me a bourbon.