Search

enum-num-num

Mark Dalrymple

4 min read

Aug 22, 2012

enum-num-num

As happens occasionally, an interesting technical discussion ensues in an IRC channel. Earlier this week, this question came up why does Apple do this to most of their enums in the Cocoa headers:

enum {
    NSRoundPlain,
    NSRoundDown,
    NSRoundUp,
    NSRoundBankers
};
typedef NSUInteger NSRoundingMode;

Instead of the more straightforward:

typedef enum {
    NSRoundPlain,
    NSRoundDown,
    NSRoundUp,
    NSRoundBankers
} NSRoundingMode;

I can’t speak for Apple’s motivation, but the first thing that jumps to mind is to have variables and struct/object members of this enum type be of a predictable size. The compiler is free to pick any storage size for an enum as it likes, so long as the largest (and most negative) values can be represented in that type.

Edit: A little birdie told me another reason is that the typedef allows for private enum values without getting a type mismatch error from the compiler

The compiler could store an NSRoundingMode in a single byte if it wished, seeing as how the values range from zero to three. Practically, the compiler is going to store enum values in a larger value for efficiency reasons. That’s pretty easy to test:

typedef enum {
    blah1 = 23
} SmallRange1;
...
printf ("sizeof unified: %zun", sizeof(SmallRange1));

Prints out

sizeof unified: 4

This make sense because an int is four bytes in 64-bit land.

Because Apple is using that split enum / typedef convention, the storage is even larger:

enum {
    blah2 = 23
};
typedef NSUInteger SmallRange2;
...
printf ("sizeof split: %zun", sizeof(SmallRange2));

Prints out

sizeof split: 8

This also makes sense because an NSUInteger is eight bytes in 64-bit land.

For the i386 (32-bit Mac) and iOS cases, both of the above enums result in storage of four bytes.

Let’s Get Small

Maybe you want your enums to have as small of a storage as possible. You can can supply the -fshort_enums compiler flag, or turn on “Short Enumeration Constants” in Xcode, to use minimal sizes.

With -fshort_enums, the SmallRange1 enum will only occupy one byte

What happens if somebody modifies the enum?

typedef enum {
    blah1 = 23,
    oopack1 = 16000
} SmallRange1;

The size of SmallRange1 will now require two bytes to hold the new value. If you were depending on the enum being a fixed size, and then suddenly it was larger after someone checked in their header file change, it could cause bugs in you code. As an example, someone is using a packed struct to overlay some data received over the network. If a one-byte interior field of that struct is now a two-byte field, the offsets for the rest of the struct will be in the wrong place.

As mentioned previously, Apple’s LLVM compiler uses an int sized storage. But, in 64-bit land, that means four bytes, not 8. By typedef‘ing the enum to an NSUInteger, it guarantees that it’ll be a 64-bit value.

New Syntax

With Xcode 4.4, the complier has brought some syntax from C++11 into Objective-C that allows you to specify the backing type when you declare the enum. The official verbiage for this is “fixed underlying type.” If you always wanted the NSRoundingMode to take two bytes, you would declare it with the type following the enum keyword. For two bytes, you can use uint16_t, or any other integer type that is 16 bites (such as a short, which is also 16 bits on the Mac and iOS)

typedef enum : uint16_t {
    NSRoundPlain,
    NSRoundDown,
    NSRoundUp,
    NSRoundBankers
} NSRoundingMode;

Now any NSRoundingMode would only take two bytes, even without -fshort-enums. Attempting to stick in a larger value will give you an error. Doing this:

typedef enum : uint16_t {
    NSRoundPlain,
    NSRoundDown,
    NSRoundUp,
    NSRoundBankers,
    NSGronk = 8675309
} NSRoundingMode;

Gives this error:

 error: enumerator value is not representable in the underlying type 'uint16_t'
      (aka 'unsigned short')
    NSGronk = 8675309
    ^

Which to Use?

A second question that came up was, “which version do you use?” If you don’t care about the size of the backing type, just directly typedef the enum. If you do care about the size, like for bit flags, or the enum might be used in a struct or in a public API, then use the fixed underlying type syntax. If that’s not available to you, then use the split enum and typedef.

A third question was “where does it go?”. If it’s part of a public API, such as a public instance variable, or a parameter to a function or method or a return type, the enum will have to go into a header file so other code can include it. If it’s purely an implementation detail, put it in a private header, or directly in the implementation file, so that readers of the header don’t get bogged down in unnecessary implementation details.

Mark Dalrymple

Author Big Nerd Ranch

MarkD is a long-time Unix and Mac developer, having worked at AOL, Google, and several start-ups over the years.  He’s the author of Advanced Mac OS X Programming: The Big Nerd Ranch Guide, over 100 blog posts for Big Nerd Ranch, and an occasional speaker at conferences. Believing in the power of community, he’s a co-founder of CocoaHeads, an international Mac and iPhone meetup, and runs the Pittsburgh PA chapter. In his spare time, he plays orchestral and swing band music.

Speak with a Nerd

Schedule a call today! Our team of Nerds are ready to help

Let's Talk

Related Posts

We are ready to discuss your needs.

Stay in Touch WITH Big Nerd Ranch News