LINUX.ORG.RU

СИ: enum VS #define

 


1

2

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

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

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

★★
Ответ на: комментарий от Bad_ptr

а так?
#define N ((int)1)

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

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

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

#define BUFFER_SIZE 1024 + sizeof(struct context)

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

Ну что поделать не все знают, зачем в макросах ставят столько скобок. А тем более блоки do{…}while(0)

А ведь еще можно сдуру написать

const int BUFFER_SIZE = 1024 + sizeof(struct context);
int arr[BUFFER_SIZE];

Не подозревая о том, что BUFFER_SIZE не константа, а переменная. Соответственно, массив будет VLA. Особенно весело выглядит конструкция вроде такой: int arr[BUFFER_SIZE] = {BUFFER_SIZE}; При том, что с макросом она работает прекрасно.

К сожалению, Си далеко не всегда может использовать такие конструкции (в том числе, и енумы) в выражениях. Вот и приходится использовтаь дефайны.

COKPOWEHEU
()

Обычно везде define.

Если речь про константы - то только define, синтаксис вида enum { x=1 } не испольую. Хотя видел в одном месте как enum-ом даже константы битовых флагов сделали и потом записывали в это поле результат OR над ними, который очевидно уже своего названия в перечислении не имел. Да, так можно, но это какой-то некрасивый костыль.

Если речь про перечисления (там, где номера пунктов не сильно важны, а важен именно список вариантов) - то иногда (по настроению, наверно, а может ещё какие мелкие причины есть) enum, но чаще всё равно define.

Считаю что enum был бы намного полезнее если б он не считался синонимом к int-у и писал хотя бы варнинг при неявном тайпкасте enum-а в int и наоборот, либо двух разных enum-ов друг в друга. И такой же варнинг при попытке произвести над enum-ом любую арифметику, кроме прибавления/вычитания к нему целого числа.

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

А ты часто смотришь дампы препроцессора? Я вот хоть и смотрел, но наверно раз в год или реже, и то не помню зачем мне это понадобилось. Много смотрел когда свой си-компилятор писал - сравнивал поведение своего препроцессора с gcc-шным.

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

Человек без опыта может вообще много чего плохого настрочить, и отлаживать он конечно (по причине неопытности) всё будет долго, но это никаким боком не аргумент в вопросе выбора стиля программирования.

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

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

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

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

Там нужна константа времени компиляции, а тип const int может отвечать константе времени выполнения. Поэтому нужен constexpr.

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

это никаким боком не аргумент в вопросе выбора стиля программирования.

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

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

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

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

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

А ты часто смотришь дампы препроцессора?

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

LamerOk ★★★★★
()

на опыт других программистов.

По опыту знаю, что enum в некоторых случаях на embedded нужно было внимательно использовать.

На обычных системах всегда привычно что enum-значения это 4 байта и обычный int, а на embedded в целях экономии на спичках компилятору говорят чтобы он для enum-значений использовал 2 байта. Внимание, код SDK драйвера ATI Imageon GPU именитой корпорации ATI/AMD:

typedef enum _AhiRotation_t {
    AhiRotateNone = 0,
    AhiRotate90,
    AhiRotate180,
    AhiRotate270,
    AhiRotateNum,
    AhiRotateMax = 0x7FFFFFFF
} AhiRotation_t;

Соответственно вот эта вот дрянь 0x7FFFFFFF это грязный хак для компилятора, чтобы он enum этот 4-байтным сделал при флагах типа -fshort-enums в GCC, а в ADS каком-нибудь оно вроде и так 2 байта из коробки.

При реверсе драйвера я однажды забыл про этот 0x7FFFFFFF и по итогу структуры были неправильного размера и флаг оказывался чёрти-где, ничего не работало. А на отладку и выявление этой особенности ушло много времени.

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

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

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

Писать везде return 0 это более чем хорошая привычка. Потому что из-за забытого return 0 в функции которая из int main() спустя какое-то время из-за внезапного рефакторинга может превратиться в int init() можно поиметь двойное UB (в случае C++) с провалом в соседний код и CVE. Личный опыт такого:

Это вообще законно? Провал выполнения в нижележащую «мёртвую» функцию

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

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

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

strlcpy

Ты вообще понимаешь, о чём говоришь? strlcpy - это BSD’шное расширение, которое не доступно в стандартных заголовках от glibc. Надо подключать <bsd/string.h>, линковать с -lbsd, а в некоторых дистрибутивах ещё и ставить дополнительно пакет libbsd-dev.

Каким образом это может незаметно сломать сборку между lts и текущей версией?

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

Note C and C++ have slightly different notice about «no return statement in function returning non-void»; In C it is only undefined if the return value is used while in C++ it is undefined the moment that the end of the function happens.

Мне иногда кажется, они это специально делают. Нет ровно ни одной причины почему поведение C и C++ в таких случаях не может быть абсолютно одинаковым, но чуваки решили просто на ровном месте говна наделать. Просто вот потому что.

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

Мне иногда кажется, они это специально делают.

Да.

Нет ровно ни одной причины

В крестах стандарт корёжат под конкретные оптимизации конкретных компиляторов не редко на стандартных же заголовках / STL.

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

С разморозкой!
https://news.ycombinator.com/item?id=36765747
Разумеется, чтобы код, его использующий собирался на LTS понадобится сначала проверить макрос и включить этот самый bsd/string.h

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

С разморозкой!

С разморозкой чего? Ты заявил, что разрабы glibc заливают тебе говна в шаровары:

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

То есть, их изменения ломают тебе код. На просьбу привести пример, ты нашёл добавление одной функции для совместимости с bsd аж в 2014-ом году, которая ничего никому не ломает, и добавление новых функций в стандарт в 2024-ом году. Такое вот «постоянно» с разрывом в 10 лет, где нет ни одной поломки обратной совместимости. Сдаётся мне, что что бы ни было у тебя в шароварах, это не от разрабов glibc.

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

В крестах стандарт корёжат под конкретные оптимизации конкретных компиляторов не редко на стандартных же заголовках / STL.

Я что-то сомневаюсь, что конкретно это UB вообще позволяет что-то оптимизировать.

Самое тупое, что в стандарте обоих языков с 2011 года есть атрибут [[noreturn]], а значит отследить конец функции с типом отличным от void вообще не является проблемой: все пути должны заканчиваться либо return, либо вызовом функции с вышеупомянутым атрибутом, в противном случае вылетает ошибка.

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

вы там уже определитесь вместе с @LamerOk, компилять с -Wall -Wpedantic или без.

$ cat main.c
int main() {}
$ gcc -Wall -Wpedantic main.c
$
$ g++ -D_PLS_NO_DEAD_CODE -Wall -Wpedantic dead_code.cpp
dead_code.cpp: In function 'int pxt_Play(int, int, char)':
dead_code.cpp:14:1: warning: no return statement in function returning non-void [-Wreturn-type]
   14 | }
      | ^

бездари

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

ты конечно.

If the return type of the main function is not compatible with int (e.g. void main(void)), the value returned to the host environment is unspecified. If the return type is compatible with int and control reaches the terminating }, the value returned to the environment is the same as if executing return 0; (since C99)

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

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

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

еще один тут начал рассказывать, что это какое-то «соглашение POSIX». На самом деле это часть стандарта C99.

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

И вот все эти люди начинают обсуждать «енумы или дефайны». «Вы же неграмотны, зачем вам подорожная?» (с)

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

про последнего с компиляцией без -Wall я вообще молчу. В продакшн без этого флага не компилируют

Скажи это @firkax, он будет категорически несогласен. И ведь его код тоже в чьём-то продакшене крутится!

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

любой редактор на шлангд

Шланг не нужен.

Пишу код в mcedit, всё норм.

int-функция без return-а в конце своей работы выглядит как минимум некрасиво, а если у тебя такого впечатления нет - ты какой-то странный.

компиляцией без -Wall я вообще молчу. В продакшн без этого флага не компилируют.

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

То есть возвращаемся к тому, что опять налицо отсутствие опыта разработки.

Отсутствие эстетического отторжения к non-void функциям без return-а как раз свидетельствует об отсутствии опыта.

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

int-функция без return-а в конце своей работы выглядит как минимум некрасиво, а если у тебя такого впечатления нет - ты какой-то странный.

Шел 2024-й, сишные комитетчики не могли осилить сделать функцию без return ошибкой компиляции вместо UB…

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

Считаю что enum был бы намного полезнее если б он не считался синонимом к int-у и писал хотя бы варнинг при неявном тайпкасте enum-а в int и наоборот, либо двух разных enum-ов друг в друга. И такой же варнинг при попытке произвести над enum-ом любую арифметику, кроме прибавления/вычитания к нему целого числа.

Сишка и нормальная система типов – не сойтись им вместе никогда.

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

Вот я тоже ожидал, что на атомиках и сильно перфу не просадит, а там вон оно как

Пока что выглядит как поклёп на разработчиков libstdc++: я затрудняюсь представить что нужно делать с std::shared_ptr чтобы конкретно вот эти atomic ops вызывали такие задержки, по крайней мере на x86. Или вы чего-то недоговариваете.

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

Там это компилятор не тупой (если конечно не самописная от производителя фигня под какую-то железяку тупую китайскую) и он в 99% случаев всё что может посчитать заранее посчитает заранее, если его специально об этом не попросить так не делать при сборке программы конечно.

anonymous
()

Для отдельных констант всегда использую define. enum использую для набора связанных констант, в 99% случаев у меня это enum state. enum как замену define для констант не использую.

Насколько я помню, для замены всех констант enum я решил не использовать, т.к. в препроцессоре это не будет работать, а разводить зоопарк из двух разных стилей мне не захотелось. Препроцессор я использую достаточно часто. Какого-то существенного преимущества у enum перед define я не увидел, чтобы это оправдывало зоопарк.

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

Зачем ты хочешь закрывать файлы? Ты думаешь, они останутся открытыми после завершения программы или что?

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

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

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

Так есть же уже, если сильно надо

typedef enum {
  RET_OK,
  RET_INVALID_PARAM,
  RET_THE_ANSWER
} FUNC_RET_t;

FUNC_RET_t test_func(int i)
{
  if (i < 0) return RET_INVALID_PARAM;
  if (i == 42) return RET_THE_ANSWER;
  return 0;
}

enum.c: In function ‘test_func’:
enum.c:15:10: warning: enum conversion from ‘int’ to ‘FUNC_RET_t’ in return is invalid in C++ [-Wc++-compat]
   15 |   return 0;
      |          ^
alx777 ★★
()
Ответ на: комментарий от alx777

Не знал. Но с++-compat скорее всего будет ругаться ещё и на слова типа new или delete используемые в качестве идентификаторов, так что не очень подходит.

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

Хотя видел в одном месте как enum-ом даже константы битовых флагов сделали и потом записывали в это поле результат OR над ними, который очевидно уже своего названия в перечислении не имел. Да, так можно, но это какой-то некрасивый костыль.

всегда так делаю. это не паскалевский enum какой-нить. в си и плюсах, это просто набор ПРОИЗВОЛЬНЫХ целочисленных констант.

любые такие константы, связанные по смыслу, образуют enum.

в смысле при работе с битами явным образом, биты, то бишь маски - это enum, а переменная с этими битами - uint, если надо - требуемой размерности. типа uint32

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