Никога ли не трябва да се използва enum в API?

Използвам C библиотека, предоставена ми вече е компилирана. Имам ограничена информация за компилатора, версията, опциите и т.н., използвани при компилирането на библиотеката. Интерфейсът на библиотеката използва enum както в структури, които се предават, така и директно като предавани параметри.

Въпросът е: как мога да гарантирам или установя, че когато компилирам код за използване на предоставената библиотека, компилаторът ми ще използва същия размер за тези enums? Ако не стане, структурите няма да се подредят и предаването на параметър може да бъде объркано, напр. 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
Като компромис, можете да използвате само enum като rvalues, когато правите свой собствен API. В c++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
Срещал съм enums в стандартна библиотека, които всички завършват с xxLastValue=-1L; /* force int size */. Винаги съм се чудил за това, но това щеше да е причината.   -  person Jongware    schedule 28.12.2013
comment
Последният параграф трябва да бъде в отговор.   -  person Keith Thompson    schedule 29.12.2013
comment
Колкото и да си струва, когато дефинирам enum, който ще се използва в компилатори, се уверявам, че има enum запис с размера, който желая. Т.Е. Ако искам да накарам моите enum елементи да бъдат минимум 16 бита, създавам ENUM_FORCE_SIZE_16 = 0xFFFF елемент в enum. Помислете за ситуация, в която (продължение)   -  person JimR    schedule 29.12.2013
comment
... системните DLL файлове се компилират с един компилатор и могат да се използват от други компилатори, като msvcrt.dll в Windows.   -  person JimR    schedule 29.12.2013
comment
@JimR: това е едновременно недостатъчно и безсмислено. Недостатъчно, тъй като различни компилатори могат да направят това два, четири или осем байта enum. Безсмислено, тъй като ABI трябва да гарантира, че enum има еднакъв размер в компилаторите. (Въпреки че не знам дали трябва == ще стане във вашия случай. Windows има ли последователен, наложен ABI?)   -  person Mark Adler    schedule 29.12.2013
comment
@MarkAdler: Тествах го на Windows (16-битов навремето) и 32-битов и работи. Също така си спомням, че го тествах с компилаторите IBM, Borland и Watcom и работеше на Windows и OS/2. Беше толкова недостатъчно, че проработи. :P   -  person JimR    schedule 02.01.2014


Отговори (2)


API, които използват enum, зависят от предположението, че компилаторът ще бъде последователен, т.е. при една и съща декларация на 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. И езикът за съвместимите типове изглежда предполага, че картографирането трябва да е последователно в отделно компилирани единици за превод, при условие че декларацията enum е същата. - person rici; 28.12.2013
comment
Като странична бележка е доста обичайно развиващият се API да добавя константи към enum в бъдещи версии. Докато дадена декларация на enum е гарантирана от ABI, че ще доведе до даден основен тип, добавянето на допълнителна константа във вашето enum МОЖЕ да промени основния тип за следващата версия на вашата библиотека. За да избегнете бъдещи проблеми, трябва да знаете основния тип за вашия ABI и се уверете, че не добавяте константа, която е несъвместима с основния тип. Ако използвате C11 компилатор, можете да си помогнете, като използвате _Static_assert, например: _Static_assert(sizeof(enum_type)==sizeof(int),Wrong Size!); - person Droopycom; 29.09.2015

От въпроса:

ABI за моята система наистина указва, че enum трябва да бъде четирибайтово цяло число със знак. Ако компилаторът не се подчинява на това, тогава това е грешка.

Изненадан съм от това. Подозирам, че в действителност вашият компилатор ще избере 64-битов (8 байта) размер за вашия enum, ако дефинирате изброена константа със стойност, по-голяма от 2^32.

На моите платформи (MinGW gcc 4.6.2, насочен към x86 и gcc 4,.4 на Linux, насочен към x86_64), следният код казва, че получавам и 4, и 8 байта enums:

#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 не предоставят enum с фиксиран размер, мисля, че единственото безопасно действие като цяло е да не се използват enum в 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