Следует ли никогда не использовать enum в API?

Я использую уже скомпилированную библиотеку C, предоставленную мне. У меня ограниченная информация о компиляторе, версии, опциях и т. д., использованных при компиляции библиотеки. Интерфейс библиотеки использует enum как в передаваемых структурах, так и непосредственно в качестве передаваемых параметров.

Вопрос в следующем: как я могу гарантировать или установить, что при компиляции кода для использования предоставленной библиотеки мой компилятор будет использовать тот же размер для этих enum? Если это не так, структуры не будут выстраиваться, и передача параметров может быть перепутана, например. long против int.

Меня беспокоит стандарт C99, в котором говорится, что тип enum:

должен быть совместим с char, целочисленным типом со знаком или целочисленным типом без знака. Выбор типа определяется реализацией, но он должен быть способен представлять значения всех членов перечисления.

Насколько я могу судить, до тех пор, пока подходит наибольшее значение, компилятор может выбрать любой тип, который ему чертовски нравится, фактически по прихоти, потенциально различаясь не только между компиляторами, но и разными версиями одного и того же компилятора и / или параметров компилятора. . Он мог выбрать 1-, 2-, 4- или 8-байтовые представления, что приводило к потенциальной несовместимости как в структурах, так и в передаче параметров. (Он также может выбрать подписанный или неподписанный, но я не вижу механизма, в котором это может быть проблемой в этом контексте.)

Я что-то упустил здесь? Если я ничего не упускаю, значит ли это, что enum никогда не следует использовать в API?

Обновление:

Да, я что-то упустил. Хотя спецификация языка здесь не помогает, как отмечает @Barmar, двоичный интерфейс приложения (ABI) помогает. Или, если это не так, то ABI несовершенен. ABI для моей системы действительно указывает, что enum должен быть подписанным четырехбайтовое целое. Если компилятор этому не подчиняется, то это ошибка. При наличии полного ABI и совместимых компиляторов enum можно безопасно использовать в API.


person Mark Adler    schedule 28.12.2013    source источник
comment
В качестве компромисса вы можете использовать перечисления только как rvalue при создании собственного API. В С++ 11 есть классы enum, которые позволяют явно указывать базовые типы. Если у вас нет контроля над API, вы все равно можете просмотреть ассемблерный код, а затем вручную заменить переменные типа enum на целые числа соответствующего размера в заголовках.   -  person Alexei Averchenko    schedule 28.12.2013
comment
Разве это не должно быть определено как часть ABI, как и размеры float, int и т. д.?   -  person dreamlax    schedule 28.12.2013
comment
Итак, да, я что-то упустил. ABI определяет или, по крайней мере, должен указывать размер enum в данной среде.   -  person Mark Adler    schedule 28.12.2013
comment
Я встречал перечисления в стандартной библиотеке, все из которых заканчивались на xxLastValue=-1L; /* force int size */. Всегда задавался вопросом об этом, но это было бы причиной.   -  person Jongware    schedule 28.12.2013
comment
Этот последний абзац должен быть в ответе.   -  person Keith Thompson    schedule 29.12.2013
comment
Что бы это ни стоило, когда я определяю перечисление, которое будет использоваться в компиляторах, я удостоверяюсь, что есть запись перечисления нужного мне размера. т.е. Если я хочу, чтобы мои элементы перечисления были как минимум 16-битными, я создаю элемент ENUM_FORCE_SIZE_16 = 0xFFFF в перечислении. Подумайте о ситуации, когда (продолжение)   -  person JimR    schedule 29.12.2013
comment
... системные библиотеки DLL компилируются одним компилятором и могут использоваться другими компиляторами, такими как msvcrt.dll в Windows.   -  person JimR    schedule 29.12.2013
comment
@JimR: этого недостаточно и бессмысленно. Недостаточно, так как разные компиляторы могут сделать это двух-, четырех- или восьмибайтовым enum. Бессмысленно, поскольку ABI должен гарантировать, что enum имеет одинаковый размер для всех компиляторов. (Хотя я не знаю, будет ли should == в вашем случае. Есть ли в Windows последовательный принудительный ABI?)   -  person Mark Adler    schedule 29.12.2013
comment
@MarkAdler: я тестировал его на Windows (16-битная в свое время) и 32-битная, и это сработало. Я также помню, как тестировал его с компиляторами IBM, Borland и Watcom, и он работал на Windows и OS/2. Этого было настолько недостаточно, что это сработало. :П   -  person JimR    schedule 02.01.2014


Ответы (2)


API, использующие enum, зависят от предположения, что компилятор будет согласованным, т. е. при одном и том же объявлении перечисления он всегда будет выбирать один и тот же базовый тип.

Хотя стандарт языка специально не требует этого, для компилятора было бы совершенно неправильно делать что-то еще.

Кроме того, все компиляторы для конкретной ОС должны соответствовать ABI ОС. В противном случае у вас было бы гораздо больше проблем, таких как библиотека, использующая 64-битную int, в то время как вызывающая сторона использует 32-битную int. В идеале ABI должен ограничивать представление enums, чтобы обеспечить совместимость.

В более общем смысле спецификация языка обеспечивает совместимость только между программами, скомпилированными с использованием одной и той же реализации. ABI обеспечивает совместимость между программами, скомпилированными с разными реализациями.

person Barmar    schedule 28.12.2013
comment
И что разные компиляторы как-то согласуются друг с другом. Возможно, разные компиляторы выбирают разные базовые типы. - person Mark Adler; 28.12.2013
comment
Все компиляторы для конкретной системы должны соответствовать ABI этой системы. - person Barmar; 28.12.2013
comment
Ага! Это отвечает на мой вопрос. Я нашел соответствующий ABI для моей системы, который действительно указывает четырехбайтовый целочисленный тип для enum. - person Mark Adler; 28.12.2013
comment
Приложение J.3.9 требует, чтобы реализация документировала отображение из enum в int. И язык о совместимых типах, кажется, подразумевает, что отображение должно быть согласованным в отдельно скомпилированных единицах перевода при условии, что объявление перечисления одинаково. - person rici; 28.12.2013
comment
Кстати, для развивающихся API довольно часто добавляют константы в перечисление в будущих версиях. Хотя ABI гарантирует, что данное объявление перечисления приведет к заданному базовому типу, добавление дополнительной константы в ваше перечисление МОЖЕТ изменить базовый тип для следующей версии вашей библиотеки. Чтобы избежать проблем в будущем, вам необходимо знать базовый тип для вашего ABI и убедиться, что вы не добавляете константы, несовместимые с базовым типом. Если вы используете компилятор C11, вы можете помочь себе, используя _Static_assert, например: _Static_assert(sizeof(enum_type)==sizeof(int),Wrong Size!); - person Droopycom; 29.09.2015

Из вопроса:

ABI для моей системы действительно указывает, что перечисление должно быть целым числом из четырех байтов со знаком. Если компилятор не подчиняется этому, то это ошибка.

Я удивлен этим. Я подозреваю, что на самом деле ваш компилятор выберет 64-битный (8 байт) размер для вашего перечисления, если вы определите перечисляемую константу со значением, превышающим 2 ^ 32.

На моих платформах (MinGW gcc 4.6.2 с таргетингом на x86 и gcc 4,.4 на Linux с таргетингом на x86_64) следующий код говорит, что я получаю как 4-, так и 8-байтовые перечисления:

#include <stdio.h>

enum { a } foo;
enum { b = 0x123456789 } bar;

int main(void) {
    printf("%lu\n", sizeof(foo));
    printf("%lu", sizeof(bar));   
    return 0;
}

Я скомпилировал с -Wall -std=c99 переключателями.

Я думаю, вы могли бы сказать, что это ошибка компилятора. Но альтернативы удаления поддержки перечислимых констант, превышающих 2^32, или всегда использование 8-байтовых перечислений кажутся нежелательными.

Учитывая, что эти распространенные версии GCC не предоставляют перечисление фиксированного размера, я думаю, что единственное безопасное действие вообще — не использовать перечисления в API.

Дополнительные примечания для GCC

При компиляции с "-pedantic" генерируются следующие предупреждения:

main.c:4:8: warning: integer constant is too large for 'long' type [-Wlong-long]
main.c:4:12: warning: ISO C restricts enumerator values to range of 'int' [-pedantic]

Поведение можно настроить с помощью переключателей --short-enums и --no-short-enums.

Результаты с Visual Studio

Компиляция приведенного выше кода с VS 2008 x86 вызывает следующие предупреждения:

warning C4341: 'b' : signed value is out of range for enum constant
warning C4309: 'initializing' : truncation of constant value

А с VS 2013 x86 и x64 просто:

warning C4309: 'initializing' : truncation of constant value
person Andrew Bainbridge    schedule 24.06.2015
comment
Интересно. Я получаю тот же результат с clang, за исключением отсутствия предупреждений! (Используя -Wall -Wextra. - person Mark Adler; 24.06.2015
comment
Я убедился, что компилятор позволяет мне поместить их в struct, без каких-либо жалоб. - person Mark Adler; 24.06.2015