Constant objects for fun and non-profit

The @"foo" operator for constant NSString objects in Objective-C is extremely convenient. Indeed, if it wasn’t there, programming with Cocoa would be a royal pain. Many of us have at various points wished there was equivalent syntax for NSNumbers, and possibly collections.

As an early Christmas present to fellow lovers of twisted, evil code that should never have seen the light of day under any circumstances whatsoever, I hereby present an implementation of the first half of that wish.

Caveat lector

Now, when I say it’s evil, I really, really mean it. We’re talking about building objects in the data section of your code by packing structs, thus depending on data layout (and as such, depending on the old 32-bit runtime). We’re talking about nested macros with lots of ?:s in them. We’re talking about gcc’s built-in parser pseudo-functions. Hearken to the words of Greg Parker, official Objective-C Runtime Wrangler at Apple:

@gparker: @ahruman JANumberLiteral would break in several exciting ways in 64-bit. Might almost be safe enough in 32-bit, though.
@ahruman: @gparker Yeah, I know. I’ll be pasting “If you do this in real life, I’ll have to track you down and shoot you” warnings all over it. :-)
@gparker: @ahruman Don't shoot 'em, just maim 'em a bit. I want my turn after you're done.
@ahruman: @gparker That’s what kneecaps were invented for.

Be told.

Overview

So, all that aside… what we’re looking at here is a macro $N(n) which takes a number and returns an NSNumber. (The convention of using dollar signs for macros implementing desired language features was introduced by the other Jens A.) If passed a compile-time constant, it will produce a constant object, i.e. a single, immortal instance based in the data section with no instantiation overhead, just like a string object constant. If passed a non-constant, it will give you a normal autoreleased NSNumber instead.

Constant NSStrings work by creating a struct with the following layout: { &__CFConstantStringClassReference, 1992, "foo", 3 }, where 1992 is a flag field and 3 is the length of the string. (Note that this is not the NSConstantString from the bottom of NSString.h. I believe this change was made in the OS X 10.2 SDK.) __CFConstantStringClassReference is a symbol exported from CoreFoundation.framework and implicitly declared in all Objective-C or Objective-C++ files by the compiler.1 My task, once I chose to accept it, was to do something similar for NSNumber.

Trying to build actual NSCFNumbers would be an unnecessary extra evil. Instead, I subclassed the abstract NSNumber twice, once for long longs and once for doubles, giving me a known layout to target. The subclasses also override -retain, -release and -autorelease to do nothing, in the manner of a singleton. Then, building an instance is conceptually simple:

const static struct { Class isa; double value; } fakeObj =
{ [JAFloatNumber class], 42.0 };
NSNumber *number = (NSNumber *)&fakeObj;

The structure needs to be declared static so that it doesn’t evaporate at the end of the containing scope. This presents a dilemma, however: static initializers must be constant expressions, and a method call doesn’t qualify. Besides, we want the object to be constant data.

Luckily for us, classes are represented by global symbols like any other. Well, almost like any other. Unlike symbols based on C identifiers, their linker symbols don’t start with an underscore but with a dot, meaning you can’t reference them the usual way. Fortunately, gcc provides us with a solution in the form of asm labels:

extern const struct objc_class JAFloatLiteralClassObject
        __asm__(".objc_class_name_JAFloatNumber");

The basic implementation then becomes:

#define JANUMBERLITERAL_CONSTANT_FLOAT(n) \
        ({ static const struct { const struct objc_class *isa; double value; } \
        object = { &JAFloatLiteralClassObject, (n) }; \
        (NSNumber *)&object; })

A problem here is that, despite all those consts, a non-const value can be used in Objective-C++. (In plain Objective-C, you get a moderately helpful warning about non-const initializers.) If you write a function that returns JANUMBERLITERAL_CONSTANT_FLOAT(random());, each instance will return the same object but its value will change, which is rather bad. The fix is to wrap this up in a macro that uses __builtin_constant_p() to ensure constant objects are only created with constant values. For non-constant values, normal autoreleased NSNumbers are created instead.

Results

The above pieces, along with a bunch of extra preprocessor stuff to handle the distinctions between Objective-C and Objective-C++ and between integer and floating point values gives us a $N(n) macro which works as advertised: if given an integer constant it returns a JAIntegerNumber, for a floating point constant it returns a JAFloatNumber, and for a non-constant it returns an autoreleased NSCFNumber. But is it really working as intended? The following code:

static NSNumber *GetNumber(void)
{
    return $N(42.0);
}

disassembles to:

    .const_data
    .align 2
_object.67163:
    .long   .objc_class_name_JAFloatNumber  // isa
    .long   0           // low bytes of 42.0
    .long   1078263808  // high bytes of 42.0

…with the actual use of the object inlined (in an optimized build) to movl $_object.67163, (%esp). The values 0 and 1078263808 correspond to the little-endian hex value of 42.0: 00000000 00004540. In other words, this really is an object as constant data. For comparison, the NSString literal used in the sample code disassembles to:

    .cstring
LC0:
    .ascii "Hello, World! %@ = %li (%@)\0"
    .section __DATA, __cfstring
    .align 2
LC1:
    .long   ___CFConstantStringClassReference // isa
    .long   1992  // flags
    .long   LC0   // bytes pointer
    .long   27    // length

Conclusion

So there you have it: fully-functional, constant NSNumber objects, as long as you don’t use the 64-bit runtime. Now don’t go actually using it, or I’ll have to shoot your kneecaps off and leave you to Greg.

Oh yeah, the code is on GitHub. For a much simpler macro that just gives you an NSNumber, automatically choosing between integer and floating-point representations, try natevw’s.


^1 Its value, on my system, is 0xa00034a0. Traditionally (pre Leopard), a value above 0xFFF would mean it’s a real Objective-C class rather than a CF type tag, but that test is no longer in the CFLite source in Leopard, so it may have changed.

If you declare extern const Class __CFConstantStringClassReference; in an Objective-C++ file, you’ll get the error “'__CFConstantStringClassReference' has a previous declaration as 'int __CFConstantStringClassReference []'”. In Objective-C, you won’t be told a type, just that it was “previously declared here”, referring to the location “<built-in>:0”.

This entry was posted in Cocoa, Code and tagged . Bookmark the permalink.

3 Responses to Constant objects for fun and non-profit

  1. Didn’t know about the __asm__ labels, handy!

  2. Jens Ayton says:

    As noted in the code, I found out about asm labels in <sys/cdefs.h>. Specifically, they’re used to build the macros that make those _thing$UNIX2003 symbols that keep giving people link errors when their SDK settings are wrong.

    For instance, when targeting 10.5, this declaration from <string.h>:
    
char *strerror(int) __DARWIN_ALIAS(strerror);

    expands to:
    
char *strerror(int) __asm("_strerror$UNIX2003");

  3. Professor Broton Chronos says:

    This is dispicable. You should be banned.

Leave a Reply

Your email address will not be published. Required fields are marked *