Поскольку значения маски являются целыми числами, мы можем представлять их по-разному. Например, в качестве макросов:
#define ENABLE_FEATURE_A 0x1
#define ENABLE_FEATURE_B 0x2
#define ENABLE_FEATURE_C 0x4
#define ENABLE_FEATURE_D 0x8
Или перечисления:
typedef enum {
FEATURE_A = 0x1,
FEATURE_B = 0x2,
FEATURE_C = 0x4,
FEATURE_D = 0x8
} enabled_features_t
Я утверждаю, что представление перечисления не подходит для масок, поскольку маски предназначены для объединения (с побитовыми операциями). C требует приведения перечислений к int, поскольку от нас не ожидают выполнения операций над числами, которые могут не иметь значения. Поэтому я думаю, что использование макросов более подходит для флагов. Я слишком много об этом думаю?
Перечислители являются целочисленными константами. Их не нужно кастовать.
@Someprogrammerdude, что ты имеешь в виду под «простым int»? Они хранятся в памяти как int, но компилятор не указывает им явный тип?
@polozer Перечисление вообще не хранится в памяти. Как я уже сказал, это символические константы int
и, как и макросы, являются константами времени компиляции, которые заменяются компилятором. Итак, предполагая перечисление, определенное в вопросе, выполнение int features = FEATURE_A | FEATURE_B;
будет скомпилировано как int features = 1 | 2;
, а это именно то, чего, похоже, хочет ОП.
Вы даже можете использовать FEATURE_AC = FEATURE_A | FEATURE_C,
в своем списке перечисления...
В C «членом» перечисления является константа перечисления типа int
, согласно C 2018 6.2.1 1:
…Член перечисления называется константой перечисления…
и 2018 6.4.4.3 2:
Идентификатор, объявленный как константа перечисления, имеет тип
int
.
Сам тип перечисления может иметь другой тип согласно C 6.7.2.2 4:
Каждый перечислимый тип должен быть совместим с
char
, целочисленным типом со знаком или целочисленным типом без знака. Выбор типа определяется реализацией,…
Я утверждаю, что представление перечисления не подходит для масок, поскольку маски предназначены для объединения (с побитовыми операциями).
Поскольку члены перечисления являются значениями int
, с ними можно работать как со значениями int
. Побитовые операции работают с ними так же, как и с другими значениями int
.
Часто предпочтительнее использовать беззнаковый тип с побитовыми операциями из-за проблем с тем, как стандарт C определяет или не определяет вычисления, включающие знаковый бит в знаковых типах. Кроме того, значения int
можно использовать в качестве битовых масок.
C требует приведения перечислений к int, поскольку от нас не ожидают выполнения операций над числами, которые могут не иметь значения.
В стандарте C такого требования нет. Возможно, вы думаете о некоторых проблемах, возникающих в C++?
Спасибо Эрик. Кажется, все согласны с тем, что в перечислениях флагов нет ничего плохого. Что касается приведения, я провел несколько экспериментов и не обнаружил никаких проблем с приведением в C или C++. Должно быть, я неправильно это запомнил.
@Lolo Согласовано, что их не следует использовать для этой цели. Авторитетные источники см., например, в правиле 10.1 MISRA C:2023.
Нет, константы перечисления не подходят для битовых масок по двум причинам:
int
, который является знаковым, поэтому его нежелательно смешивать, в частности, с побитовыми операциями, поскольку многие побитовые операции, такие как сдвиги, вызывают различные формы плохо определенного поведения при использовании со знаковыми типами.0x80000000
в константу перечисления, поэтому его практически невозможно использовать для битовой маскировки 32-битного числа.На сегодняшний день подходящим решением (ISO C 9899:2018) является использование #define
с целочисленными константами, оканчивающимися суффиксом U
/u
.
Однако будущий стандарт C23 решит эту проблему. Это станет возможным использовать:
typedef enum : uint32_t
{
FEATURE_X = 0x80000000
} enabled_features_t;
Теперь эквивалентным типом, используемым для enum
объектов этого типа, а также константы перечисления, является uint32_t
.
Спасибо. Меня беспокоил кастинг, в котором я ошибался, но я не думал о проблеме int и uint и проблеме размера.
В C перечисление — это, по сути, способ введения именованных символических констант
int
, как и в макросах. По сути, это простыеint
ценности. Итак, в вашем коде нет никакой разницы между макросомENABLE_FEATURE_A
и символом перечисленияFEATURE_A
.