枚举值:使用 NSInteger 还是 int?

39

简短版

当这样声明枚举时,枚举常量的数据类型如何保证为NSUInteger而不是unsigned int:

enum {
    NSNullCellType = 0,
    NSTextCellType = 1,
    NSImageCellType = 2
};
typedef NSUInteger NSCellType;

typedef到NSUInteger似乎与枚举声明没有任何关联。

完整版本

我正在阅读苹果公司的64位Cocoa转换指南,以获取有关枚举值的指导,并产生了一个问题。这是枚举常量部分的一段(冗长的)引用,重点在于我:

枚举(enum)常量的问题在于它们的数据类型经常是不确定的。换句话说,枚举常量不能可预测地为unsigned int。对于传统构造的枚举,编译器实际上根据其所发现的内容设置底层类型。底层类型可以是(有符号的)int或甚至是long。看下面的例子:

type enum {
    MyFlagError = -1,
    MyFlagLow = 0,
    MyFlagMiddle = 1,
    MyFlagHigh = 2
} MyFlagType;

编译器看到这个声明,并且发现一个成员常量被赋了一个负值,它会声明枚举的基础类型为int。如果成员的值的范围不适合int或unsigned int,则基础类型会静默地变为64位(long)。因此,作为枚举定义的量的基础类型可以根据枚举中的值而静默地改变大小。无论您是在编译32位还是64位,都可能发生这种情况。不用说,这种情况对二进制兼容性构成了障碍。 为解决这个问题,苹果公司决定在Cocoa API中更明确地指定枚举类型。现在,头文件单独声明了一个可指定大小的枚举类型,而不是以枚举形式声明参数。枚举的成员及其值如之前所述进行声明和赋值。例如,不再使用以下方式:
typedef enum {
    NSNullCellType = 0,
    NSTextCellType = 1,
    NSImageCellType = 2
} NSCellType;

现在有这个:
enum {
    NSNullCellType = 0,
    NSTextCellType = 1,
    NSImageCellType = 2
};
typedef NSUInteger NSCellType;

枚举类型是以 NSInteger 或 NSUInteger 定义的,以便在 64 位体系结构上使基本枚举类型具备 64 位能力。
我的问题是:鉴于 typedef 看起来并没有明确地与枚举声明相关联,那么如何知道它们的数据类型是 unsigned int 还是 NSUInteger?
4个回答

53

现在在 Xcode 4.5 中有一个 NS_ENUM

typedef NS_ENUM(NSUInteger, NSCellType) {
    NSNullCellType = 0,
    NSTextCellType = 1,
    NSImageCellType = 2
};

如果您使用二进制标志,可以考虑使用NS_OPTIONS

typedef NS_OPTIONS(NSUInteger, MyCellFlag) {
    MyTextCellFlag = 1 << 0,
    MyImageCellFlag = 1 << 1,
};

2
请确保在进行此操作时,永远不要意外地将负值放入枚举中。这会导致Xcode在编译过程中挂起,而没有任何警告或错误提示。如果您从NSUInteger开始为enum类型赋值,然后稍后决定给它一个负值并忘记从类型中删除U,那么很容易出现这种情况。希望这能为某些人节省时间。 - Beltalowda
1
hang的问题可能来自编译器试图分配所有中间枚举值,即在32位上大约有40亿个值。 - Cœur

17
我在模拟器上运行了一个测试,目的是检查不同整数类型的大小。为此,在控制台打印了 sizeof 的结果。所以我测试了这些 enum 值:
      
typedef enum {
    TLEnumCero = 0,
    TLEnumOne = 1,
    TLEnumTwo = 2
} TLEnum;

typedef enum {
    TLEnumNegativeMinusOne = -1,
    TLEnumNegativeCero = 0,
    TLEnumNegativeOne = 1,
    TLEnumNegativeTwo = 2
} TLEnumNegative;

typedef NS_ENUM(NSUInteger, TLUIntegerEnum) {
    TLUIntegerEnumZero = 0,
    TLUIntegerEnumOne = 1,
    TLUIntegerEnumTwo = 2
};

typedef NS_ENUM(NSInteger, TLIntegerEnum) {
    TLIntegerEnumMinusOne = -1,
    TLIntegerEnumZero = 0,
    TLIntegerEnumOne = 1,
    TLIntegerEnumTwo = 2
};

测试代码:


    NSLog(@"sizeof enum: %ld", sizeof(TLEnum));
    NSLog(@"sizeof enum negative: %ld", sizeof(TLEnumNegative));
    NSLog(@"sizeof enum NSUInteger: %ld", sizeof(TLUIntegerEnum));
    NSLog(@"sizeof enum NSInteger: %ld", sizeof(TLIntegerEnum));

iPhone Retina (4英寸) 模拟器的结果:


sizeof enum: 4
sizeof enum negative: 4
sizeof enum NSUInteger: 4
sizeof enum NSInteger: 4

iPhone Retina (4-inch 64 bit) 模拟器 的结果:


sizeof enum: 4
sizeof enum negative: 4
sizeof enum NSUInteger: 8
sizeof enum NSInteger: 8

结论

通用的enum可以是32位或者64位的intunsigned int类型,均占据4字节。 我们已经知道,在iOS的32位编译器中,NSUIntegerNSInteger均为4字节;在64位编译器中则分别为8字节。


1
@javionegas 感谢您的测试,但是您在32位和64位之间交换了结果。 - klefevre

8

这是两个不同的声明。typedef保证了当你使用该类型时,总会得到一个NSUInteger。

enum的问题并不在于它不能容纳足够大的值。事实上,你对于一个enum唯一得到的保证是sizeof(enum Foo)足够大,以容纳当前在该enum中定义的任何值。但如果你添加另一个常量,它的大小可能会改变。这就是为什么Apple要做单独的typedef,以维护API的二进制稳定性。


创建的类型(例如NSCellType)不会比enum的大小小,这是什么保证?在此方案下,它总是基于最宽可用类型吗? - jscs
1
只要你在typedef中使用的类型可以表示枚举常量,那么枚举使用的大小就不重要了。例如,short x = 1LL将在系统上正确地给出一个值为1的short,即使long long比short宽四倍。 - Chuck
@Chuck:好的,明白了。我的想法正好相反:假设有这样一个声明 enum { JCWishy = (1 << 0), ..., JCWashy = (1 << 63) };,那么至少需要使用8字节类型来用于 typedef xxx JCShy,除非有某种编译器强制执行(我看不到机制)。否则,JCShy j = JCWashy; 就无法工作。 - jscs
@JoshCaswell:是的,这就是我说的。您正在声明一个需要8个字节的常量,因此您在typedef中的类型必须至少为8个字节宽。但是枚举类型本身的大小并不重要。 - Chuck
@Chuck:哦,对不起,我以为你是反过来的意思。谢谢。 - jscs

2
枚举常量的数据类型不能保证为NSUInteger,但每次通过NSCellType使用它们时都保证将其强制转换为NSUInteger
换句话说,声明规定尽管枚举值目前适合于unsigned int,但在通过NSCellType访问时为它们保留的存储应该是NSUInteger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接