значения перечисления: NSInteger или int?

tl; dr Версия

Каким образом типы данных констант перечисления гарантированно будут NSUInteger вместо unsigned int при объявлении перечисления таким образом:

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

Typedef для NSUInteger, похоже, никоим образом не привязан к объявлению перечисления.

Полная версия

Я читал 64-битное руководство по переходу для какао от Apple. для некоторых рекомендаций по значениям перечисления, и я ушел с вопросом. Вот (длинная) цитата из раздела Константы перечисления, выделено мной:

Проблема с константами перечисления (enum) заключается в том, что их типы данных часто неопределенны. Другими словами, константы перечисления не являются предсказуемо беззнаковыми int. При использовании перечислений, построенных традиционным способом, компилятор фактически устанавливает базовый тип на основе того, что он находит. Базовый тип может быть (подписанным) int или даже длинным. Возьмем следующий пример:

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

Компилятор просматривает это объявление и, найдя отрицательное значение, присвоенное одной из констант-членов, объявляет базовый тип перечисления int. Если диапазон значений для членов не помещается в int или unsigned int, тогда базовый тип автоматически становится 64-битным (длинным). Таким образом, базовый тип величин, определенных как перечисления, может незаметно изменять размер в соответствии со значениями в перечислении. Это может произойти независимо от того, компилируете ли вы 32-битную или 64-битную версию. Излишне говорить, что такая ситуация создает препятствия для двоичной совместимости.

В качестве решения этой проблемы Apple решила более четко указать тип перечисления в 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?


person Michael Fey    schedule 13.12.2011    source источник


Ответы (4)


Теперь NS_ENUM запускается Xcode 4.5:

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

И вы можете рассмотреть NS_OPTIONS, если вы работаете с бинарными флагами:

typedef NS_OPTIONS(NSUInteger, MyCellFlag) {
    MyTextCellFlag = 1 << 0,
    MyImageCellFlag = 1 << 1,
};
person Cœur    schedule 13.11.2012
comment
Просто убедитесь, что при этом вы никогда случайно не поместите отрицательное значение в перечисление. Это приведет к зависанию Xcode во время компиляции без каких-либо предупреждений или ошибок. Это легко сделать, если вы начнете с NSUInteger для типа enum, а затем решите, что хотите дать ему отрицательное значение, и забудете удалить U из типа. Надеюсь, это кому-то сэкономит время. - person Beltalowda; 07.04.2015
comment
Зависание, вероятно, происходит из-за того, что компилятор пытается выделить все промежуточные значения перечисления, что означает примерно 4 миллиарда значений на 32 битах. - person Cœur; 10.04.2015

Я провожу тест на симуляторе, поэтому цель теста - проверить размер различных целочисленных типов. Для этого результат 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));

Результат для имитатора Retina (4 дюйма) iPhone:


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

Результат для iPhone Retina Simulator (4 дюйма, 64-разрядная версия):


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

Заключение

Общий enum может быть типа int или unsigned int по 4 байта для 32 или 64 бит. Как мы уже знаем, NSUInteger и NSInteger - это 4 байта для 32 бит и 8 байтов в 64-битном компиляторе для iOS.

person javienegas    schedule 07.01.2014
comment
@javionegas Спасибо за тест, но вы поменяли местами результаты с 32-битной на 64-битную. - person klefevre; 06.02.2014

Это два отдельных объявления. Typedef гарантирует, что при использовании этого типа вы всегда получите NSUInteger.

Проблема с перечислением не в том, что оно недостаточно велико для хранения значения. Фактически, единственная гарантия, которую вы получаете для перечисления, заключается в том, что sizeof (enum Foo) достаточно велик, чтобы содержать любые значения, которые вы в настоящее время определили в этом перечислении. Но его размер может измениться, если вы добавите еще одну константу. Вот почему Apple использует отдельный typedef, чтобы поддерживать бинарную стабильность API.

person uliwitness    schedule 13.12.2011
comment
Что гарантирует, что созданный тип (например, NSCellType) не меньше размера enum? Всегда ли он должен быть основан на самом широком доступном типе по этой схеме? - person jscs; 14.12.2011
comment
@JoshCaswell: до тех пор, пока константы, которые вы объявляете для перечисления, могут быть представлены в типе, который вы используете в typedef, не имеет значения, какой размер используется перечислением. Например, short x = 1LL правильно даст вам короткую позицию со значением 1 даже в системах, где длинный длинный в четыре раза шире короткого. - person Chuck; 14.12.2011
comment
@Chuck: Верно, это ясно. Я думал об обратном: это (гипотетическое) объявление типа enum { JCWishy = (1 << 0), ..., JCWashy = (1 << 63) }; потребовало бы использования по крайней мере 8-байтового типа для typedef xxx JCShy, если только не было какого-либо принудительного исполнения компилятора (для которого я не вижу механизма) . Иначе JCShy j = JCWashy; не сработает. - person jscs; 14.12.2011
comment
@JoshCaswell: Да, это то, что я сказал. Вы объявляете константу, для которой требуется 8 байтов, поэтому тип в вашем typedef должен иметь ширину не менее 8 байтов. Но не имеет значения, какого размера сам тип перечисления. - person Chuck; 14.12.2011
comment
@Chuck: Ой, извини, я думал, ты имел в виду обратное. Спасибо. - person jscs; 14.12.2011

Не гарантируется, что типы данных констант перечисления будут NSUInteger, но они гарантированно будут преобразованы в NSUInteger каждый раз, когда вы используете их через NSCellType.

Другими словами, объявление указывает, что, хотя значения перечисления в настоящее время вписываются в unsigned int, хранилище, зарезервированное для них при доступе через NSCellType, должно быть NSUInteger.

person hatfinch    schedule 14.12.2011