LINUX.ORG.RU

СИ: enum VS #define

 


0

2

У меня зачастую бывают такие ситуации, что я как осёл между двумя стогами сена стою и не могу для себя решить, что использовать в данном конкретном случае.

Напишите, чем вы руководствуетесь, когда решаете использовать дефайны или енумы.

P.S. Тема не имеет под собой какого-то конкретного случая. Просто хочу посмотреть на опыт других программистов.

★★

Еслт что-то простое число, условные «штуки», то могу использовать дефайн. Если что-то используется в логике, a не просто арифметике, то это что-то заслуживает своего типа и потому enum.

tempUser
()

Препроцессорная фигня вообще моветон и используется только когда нужно код генерировать очень сильно, например, бекдор воткнуть чтоб его не так палевно было видно. Хотя я больше люблю питоном код генерировать на любом ЯП, чем пользоваться уродцами вроде дефайнов, потому как питон всяко гибче и позволяет машинную/однотипную фигню короче и лаконичнее писать. Особенно сложные запросы к субд для переделки табличной структуры, которые грубо говоря надо разово запускать и вылизывать по скорости выполнения не требуется.

peregrine ★★★★★
()

Я не программист и не опытный, но, всё зависит от желания твоей левой пятки

  • Если это константы уровня API или чисто внутреннего представления то enum

  • Если это меняемые константы которые надо менять во время сборки то это define порой ещё вычисляемые, установка -DFOO приводит к смене значения define BAR через ifdef FOO

  • Это может быть утилитарной вещью, ну типа как тебе удобнее так и делаешь и всё.

  • Опять же ничто тебе не мешает передалать define в emum или наоборот потом, это никак не поменяет работу программы.

  • enum ограничен интами, а это значит что если ты через emum хочешь менять например строку в компилируемой единице тебе придётся строить lookup табличку которая будет иметь нужные строки, где значения enum будут ключами, это норм, если у тебя некое определённое состояние, но если ты хочешь подменять произвольную строку то опять же define который можно задать произвольно во время сборки, или через подключение заголовочных файлов где нужное тебе уже вписано.

  • define если учитывать его суть, может пойти как экономия вычислений, ведь ты просто заранее подставляешь константу по месту, а не вычисляешь (получить данные это тоже вычисление)

В целом если это чиселки то почти без разницы, если у тебя интерфейс с флагами, ну храни их в enum если хочется и нужно эти флаги менять то define, а можно и то и то строить emun с идентификаторами как частью внешнего или внутреннего API, а задавать им значения через define

Вариантов столь много что всё учесть нельзя, продолжать их перебирать нет смысла. Начинай всегда просто с чего-то одного, например с define прописывай константы, накопятся разные и числовые и не числовые, потом либо просто оставишь всё как есть, либо чистовые перенесёшь в enum может просто для красоты например или типа фиксации.

Благо тут переделать всё просто, так что просто подбрось монетку.

LINUX-ORG-RU ★★★★★
()

Если это семантически enum, то есть несколько связанных значений типа режимов работы функции, типов объекта или ещё чего, то enum. Компилятор C не будет толком проверять типы, но хотя бы программист будет сразу видеть, что переменная не обычный int. Самодокументируемый код это хороший тон.

Если это просто константа (например, размер какого-то буфера, магическое число и т. д.), то define.

Если нужна возможность менять значение при сборке через -D (build-time конфигурация), то define.

Если хочется навернуть какую-то логику на ifdef и прочих препроцессорных штуках (часто применяется с предыдущим пунктом, хотя иногда источник конфигурации другие исходные файлы или встроенные директивы компилятора), то define.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 8)

enum короче (не нужно писать #define), а значит и удобнее для написания. Но использую лишь когда у меня есть группа связанных по смыслу значений.

jpegqs
()
Ответ на: комментарий от LamerOk

Стандартом языка. enum имеет тип int.

Уже нет.

C ➤ cat enum.c                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
#include <stdio.h>

enum E : unsigned char { A, B, C };

int main(void) {
  printf("%lu\n", sizeof(A));
  return 0;
}
C ➤ gcc enum.c -o enum && ./enum                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
1
hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)

мне интересно, тут кто-нибудь вообще собирался написать про основное преимущество enum перед дефайнами? То что их названия не теряются после прохода препроцессора, gcc -E и вот это все, что серьезно упрощает чтение кода в подобных ситуациях.

…или как обычно, тут все сишники только на словах, но не в резюме?))

Lrrr ★★★★★
()
Ответ на: комментарий от hateyoufeel

вот меня поражает тяга кучи людей писать бессмысленный return 0 в конце функции.

Еще и какие-то аргументы в его пользу пытаются придумывать, а на самом деле тупо не знают язык, лол))

Lrrr ★★★★★
()
Ответ на: комментарий от Lrrr

вот меня поражает тяга кучи людей писать бессмысленный return 0 в конце функции.

У меня emacs создаёт шаблон с return 0 в main() лол

мне интересно, тут кто-нибудь вообще собирался написать про основное преимущество enum перед дефайнами?

В си его нет. В C++ enum class рулит.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от Lrrr

Ты оперируешь лишь одним фактом, ставя его во главе угла, но это не так. Си это не про «как правильно» для какого-то узкого случая. Даже #include "source.c" имеет место быть в определённых ситуациях, и не просто место, а важное место. Если тебе важны имена идентификаторов после препоцессинга, да не используй define. Сишник это не тот кто делаешь всё по каким-то «правильным» шаблонам, сам язык предполагает обратное.

мне интересно, тут кто-нибудь вообще собирался написать про основное преимущество enum перед дефайнами?

Нет никаких преимуществ, это просто разные вещи, которые имеют в неких граничных условиях возможность использовать их для одного и того-же.

Это как выбирать между колесом и гусеницами, их преимущества проявляются только в конкретных случаях, для чего они конкретно созданы, но вне этих рамок, и на тех и на тех можно кататься, если кататься нужно в условиях где без разницы что на гусеницах что на колёсах то и без разницы. А есть ещё вещи сбоку, требуемая мощность двигателя, жор топлива и т.д. Так и с define vs enum, важна семантика, это просто чисто PI которое надо просто расставить где надо, или это идентификатор для API и даже не сейчас, а потом.

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

но не в резюме?))

Тут ты прав, резюме у меня нет, лично я лишь балаболю :)
Давай вместе подождём настоящих программистов, и ты кажется тоже :D

Хотя буду рад если поправишь или контраргументируешь.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от hateyoufeel

Стандартом языка. enum имеет тип int.

Уже нет.
Уже
C23
was published on October 31, 2024

Когда к дате публикации добавят лет 10, вот тогда будет "уже". А пока "ещё только собирается в будущем".

LamerOk ★★★★★
()
Ответ на: комментарий от KivApple

Если это просто константа (например, размер какого-то буфера, магическое число и т. д.), то define.

С появлением const и inline в C99 нет причин использовать дифайн для чисел.

С дифайном легко обосраться в самых неожиданных местах, его вообще не следует использовать, а если использовать - то только как самую крайнюю меру, когда нет других средств языка для решения задачи.

К примеру, человек без опыта легко может настрочить что-нибудь в духе:

#define BUFFER_SIZE 1024 + sizeof(struct context)

а потом долго отлаживать краши.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

С появлением const и inline в C99 нет причин использовать дифайн для чисел.

Почему же

#if (NUM > 10)

Да и для размеров массивов тут const не сильно поможет - он всё ещё символ, который пусть и константный, но не обязательно значение известно на стадии компиляции.
Про c++ после появления constexpr частично соглашусь - он действительно покрывает большинство применений препроцессорных констант, однако даже там применение if constexr ограничено и появился он только в c++17, до этого разве что нечитаемая каша из SFINAE, если код необходимо действительно отключить т.к он становится невалидным

mittorn ★★★★★
()
Последнее исправление: mittorn (всего исправлений: 1)
Ответ на: комментарий от mittorn

Да и для размеров массивов тут const не сильно поможет - он всё ещё символ, который пусть и константный, но не обязательно значение известно на стадии компиляции.

В C теперь тоже есть constexpr лол.

Но вообще, нет ровно ни одной причины использовать C вместо C-подобного подмножества C++.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от mittorn

Да и для размеров массивов тут const не сильно поможет

Как раз ровно для них и есть enum. const нужен для плавающий точки и в общем случае предпочтителен для любой арфметики.

Почему же

#if (NUM > 10)

А вот таким глотанием шпаг на препроцессоре в прыжке сквозь горящий обруч вообще не надо заниматься. Нет объективных оправданий причин.

LamerOk ★★★★★
()
Ответ на: комментарий от hateyoufeel

Но вообще, нет ровно ни одной причины использовать C вместо C-подобного подмножества C++.

То, что он может вставить какой-нибудь __cxa_guard_acquire где его не просили - вполне себе причины. Некоторые требования c++ после 11 стандарта неплохо могут так поломать оптимизацию, код писать приходится заметно осторожнее чем C.
Если в Си ты отстрелишь только ногу, в C++ выстрел снесёт всё тело

mittorn ★★★★★
()
Ответ на: комментарий от LamerOk

Вполне может быть полезно если код получаюшийся при невыполненном условии невалиден. Даже в плюсах иногда оно нужно. Простой пример где подобная конструкция нужна - сравнение дефайна с версией стандарта C/C++ или компилятора - на компиляторе, для которого условие не выполнено код под условием не скомпилируется или создаст проблемы. В си же подобным конструкциям просто нет альтернативы - там вместо шаблонов препроцессор и потому константы нужны именно в нём

mittorn ★★★★★
()
Ответ на: комментарий от mittorn

То, что он может вставить какой-нибудь __cxa_guard_acquire где его не просили - вполне себе причины.

-fno-threadsafe-statics, если ты так в себе уверен. Ну или, я не знаю, не пиши классы со статическими членами?

Некоторые требования c++ после 11 стандарта неплохо могут так поломать оптимизацию

Какие? Хочу примеров.

Если в Си ты отстрелишь только ногу, в C++ выстрел снесёт всё тело

Да нет, сишники тут уже третий холокост по кругу сами себе и друг другу устраивают, и им всё мало. Язычки примерно одного уровня всратости, но плюсы хотя бы позволяют плодить меньшее говно.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 2)

если есть возможность не использовать препроцессор, то не использую его :-)

так что enum'ы и инлайновые функции. Нехай компилятор типы проверяет.

demidrol ★★★★★
()
Ответ на: комментарий от hateyoufeel

Так threadsafe static'и повлияли на уже существующий код. Где гарантия, что в следующем стандарте они не попытаются сделать на всех статиках/глобалках неявный атомик? В то время, код на C пока что не обрастает подобными неявностями

Какие? Хочу примеров.

так threadsafe static как раз один из примеров - эти блокировки препятствуют оптимизациям кода вокруг них т.к являются барьерами. Другой пример больше касается STL, но тем не менее: объект, в котором приблизительно 100500 shared pointer'ов. Программа висела секунд 10 на выходе в деструкторе, каждый из которых захватывал лок, что-то делал, после чего отпускал его. Конечно решилось заменой STL'овой лапши на свою реализацию, для которой thread safety была уже опциональной.

mittorn ★★★★★
()
Ответ на: комментарий от mittorn

сравнение дефайна с версией стандарта C/C++ или компилятора - на компиляторе, для которого условие не выполнено код под условием не скомпилируется

В пользовательском коде? Ну так и пусть не компилируется. Требования к стандарту всё равно прописываются в системе сборки.

Проверки уровня #if (__STDC_VERSION__ >= 201112L) - это для системных библиотек, у которых хидер-файлы существуют в единственном экземпляре и включаются при разных версиях используемого стандарта. В таком сценарии это действительно оправдано, но это крайне специфическая история, которая точно не нужна людям, начинающим программировать (а топик-то, на минуточку об этом!).

LamerOk ★★★★★
()
Ответ на: комментарий от mittorn

объект, в котором приблизительно 100500 shared pointer’ов. Программа висела секунд 10 на выходе в деструкторе, каждый из которых захватывал лок,

Подожди, но ты же сам сначала захотел 100500 объектов с локом, а потом жалуешься, что они этот лок используют.

Некоторые требования c++ после 11 стандарта неплохо могут так поломать оптимизацию

Каким образом ```std::shared_ptr``, который только появился в 11-ом стандарте, может поломать оптимизацию кода, написанного до появления 11-ого стандарта?

LamerOk ★★★★★
()
Ответ на: комментарий от mittorn

Другой пример больше касается STL, но тем не менее: объект, в котором приблизительно 100500 shared pointer’ов. Программа висела секунд 10 на выходе в деструкторе, каждый из которых захватывал лок, что-то делал, после чего отпускал его.

А кто их туда засунул? Кто их туда засунул, га?

Конечно решилось заменой STL’овой лапши на свою реализацию, для которой thread safety была уже опциональной.

Ну и балда. В нормальном мире для закрытия программы просто делают отдельный code path с деинициализацией только внешних ресурсов (файлы, сокеты и т.д.).

Только всё это не является свойством C++ ни в коей мере. Гномеры вот ровно такое же переусложнённое говно на чистом C лепят.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от LamerOk

Ну так и пусть не компилируется.

Ну и живи вэтом проклятом мире, где ничего не собирается
В пользовательском коде оно конечно не сильно нужно, но с учётом постоянно добавляемых в glibc функций хотелось бы чтобы код собирался и в lts и в апстриме, а функции при этом использовать о возможности

mittorn ★★★★★
()
Ответ на: комментарий от LamerOk

Этот код был написан под 17й стандарт, однако, то, что сделали в стандарте с принудительной потокобезопасностью оказалось краайне неоптимальным

mittorn ★★★★★
()
Ответ на: комментарий от mittorn

объект, в котором приблизительно 100500 shared pointer’ов… каждый из которых захватывал лок… Конечно решилось заменой STL’овой лапши на свою реализацию, для которой thread safety была уже опциональной.

Я может пропустил чего - в какой момент в std::shared_ptr полновесные «локи» появились? Там же на атомиках всегда всё было.

bugfixer ★★★★★
()
Ответ на: комментарий от cobold

Ну, вообще-то нет, не только это. В эмбеддинге, скажем, на какую-то константу типа размера буфера не хочется тратить место в .const, но и писать её руками в десяти местах тоже бред. А если она ещё и вычисляется из других констант на этапе компиляции, дефайн единственный вариант.

anonymous
()
Ответ на: комментарий от LamerOk

Опять же, с т.з. эмбеддинга, глобальный const создаёт объект в секции .const, это может быть излишним. inline - вообще не больше, чем рекомендация, на которую компилятор может забить. Учитесь смотреть на мир за пределами вашей десктопной коробки, пригодится.

anonymous
()
Ответ на: комментарий от Lrrr

вот меня поражает тяга кучи людей писать бессмысленный return 0 в конце функции.

Потому что функция без return может привести к катастрофическим последствием вроде выполнения кода следующей функции и порчи памяти/стека. Да, для main есть исключение, но лучше им не пользоваться чтобы не попасть на лишние грабли.

X512 ★★★★★
()

Не нравится использовать enum для объявления целочисленных констант, для этих целей использую #define.

Для перечисляемого типа использую enum, если не важно, сколько он будет занимать памяти (он размера int, если не указанно иное во флагах компилятора), если важно, то использую char или short.

zx_gamer ★★★
()
Ответ на: комментарий от LamerOk

enum лучше работает в качестве настоящих констант, чем const, например:

enum {va = 11};
const int vb = 12;

int main() {
    int num = 123;
    switch (num) {
        case va: break;
        case vb: break;
    }
}

получаем:

    <source>:8:9: error: case label does not reduce to an integer constant
    8 |         case vb: break;
      |         ^~~~

Ну не прелестно ли, const int - это не integer constant?

unC0Rr ★★★★★
()
Ответ на: комментарий от unC0Rr

Ну не прелестно ли, const int - это не integer constant?

Но ведь таки действительно не integer constant.

То же самое почему

const int x = 123;
int arr[x];

arr – это variable length array.

a1ba ★★
()
Последнее исправление: a1ba (всего исправлений: 1)
Ответ на: комментарий от mittorn

Ну и живи вэтом проклятом мире, где ничего не собирается

Я в нём живу уже больше четверти века, и у меня почему-то всё, что мне надо, собирается.

с учётом постоянно добавляемых в glibc функций хотелось бы чтобы код собирался и в lts и в апстриме

Это где у тебя такая разница между lts и апстримом, где есть " постоянно добавляемые в glibc функции"? Примеры можешь привести или будет как с std::shared_ptr?

LamerOk ★★★★★
()
Последнее исправление: LamerOk (всего исправлений: 1)
Ответ на: комментарий от Lrrr

вот меня поражает тяга кучи людей писать бессмысленный return 0 в конце функции.

Ты же ни разу в жизни программу с -Wall не собирал, не говоря уж о -Wpedantic.

LamerOk ★★★★★
()
Ответ на: комментарий от anonymous

Опять же, с т.з. эмбеддинга,

Я тебе искренне сочувствую, если твой компилятор не умеет в оптимизации от слова совсем. Но тупой компилятор и ограничение ресурсов на железке не оправдание для написания кривого кода в общем случае. Это частный случай.

Учитесь смотреть на мир

Нет, спасибо.

пригодится

Господь милостив, он не допустит!

LamerOk ★★★★★
()