Что делает лучшую константу в C, макросе или перечислении?

Я смущен тем, когда использовать macros или enums. Оба могут использоваться как константы, но в чем разница между ними и в чем преимущество одного из них? Это как-то связано с уровнем компилятора или нет?

С точки зрения удобочитаемости, enums создают лучшие константы, чем macros, поскольку связанные значения группируются вместе. Кроме того, enum определяет новый тип, поэтому читателям вашей программы было бы легче узнать, что можно передать соответствующему параметру.

сравнить

 #define UNKNOWN 0 #define SUNDAY 1 #define MONDAY 2 #define TUESDAY 3 ... #define SATURDAY 7 

в

 typedef enum { UNKNOWN , SUNDAY , MONDAY , TUESDAY , ... , SATURDAY } Weekday; 

Намного проще читать код, подобный этому

 void calendar_set_weekday(Weekday wd); 

чем это

 void calendar_set_weekday(int wd); 

потому что вы знаете, какие константы в порядке, чтобы пройти.

Макрос – это препроцессор, а скомпилированный код не имеет представления о создаваемых вами идентификаторах. Они уже заменены препроцессором до того, как код попадает в компилятор. Перечисление – это объект времени компиляции, а скомпилированный код сохраняет полную информацию о символе, который доступен в отладчике (и других инструментах).

Предпочитайте enums (когда сможете).

В C лучше всего использовать enums для фактических перечислений: когда какая-либо переменная может содержать одно из нескольких значений, которым могут быть присвоены имена. Одно из преимуществ перечислений состоит в том, что компилятор может выполнять некоторые проверки, кроме того, что требуется языку, например, что оператор switch для типа enums не пропускает один из этих случаев. Идентификаторы enums также распространяются на информацию об отладке. В отладчике вы можете увидеть имя идентификатора как значение переменной enums, а не только числовое значение.

Перечисления могут использоваться только для побочного эффекта создания символических констант интегрального типа. Например:

 enum { buffer_size = 4096 }; /* we don't care about the type */ 

эта практика не так широко распространена. Во- buffer_size , buffer_size будет использоваться как целое число, а не как перечисляемый тип. Отладчик не будет отображать 4096 в buffer_size , потому что это значение не будет представлено как перечисляемый тип. Если вы объявите некоторый char array[max_buffer_size]; то sizeof array не будет отображаться как buffer_size . В этой ситуации константа enums исчезает во время компиляции, поэтому она также может быть макросом. И есть недостатки, такие как неспособность контролировать его точный тип. (В некоторой ситуации может быть небольшое преимущество, когда вывод шагов предварительной обработки перевода фиксируется как текст. Макрос превратится в 4096, а buffer_size останется в качестве buffer_size ).

Символ препроцессора позволяет нам сделать это:

 #define buffer_size 0L /* buffer_size is a long int */ 

Обратите внимание, что различные значения из C такие как UINT_MAX являются символами препроцессора, а не перечисляемыми символами, что является веским основанием для этого, поскольку эти идентификаторы должны иметь точно определенный тип. Другим преимуществом символа препроцессора является то, что мы можем проверить его присутствие или даже принимать решения на основе его значения:

 #if ULONG_MAX > UINT_MAX /* unsigned long is wider than unsigned int */ #endif 

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

Перечисления также плохо подходят для битмасков:

 enum modem_control { mc_dsr = 0x1, mc_dtr = 0x2, mc_rts = 0x4, ... } 

это просто не имеет смысла, потому что, когда значения объединены с побитовым ИЛИ, они производят значение, которое находится за пределами типа. Такой код также вызывает головную боль, если он когда-либо портирован на C ++, который имеет (несколько более) нумерацию типов.

Обратите внимание, что есть несколько отличий между макросами и enumsми, и любое из этих свойств может сделать их (un) подходящими в качестве конкретной константы.

  • перечислены подписываются (совместимы с int). В любом контексте, где требуется неподписанный тип (например, особенно побитовые операции!), Перечисления отсутствуют.
  • если long long шире int, большие константы не будут вписываться в перечисление.
  • Размер enums – sizeof(int) . Для массивов небольших значений (например, CHAR_MAX ) вам может понадобиться char foo[] а не массив enum foo[] .
  • enums являются целыми числами. Вы не можете enum funny_number { PI=3.14, E=2.71 } .
  • enums – функция C89; Компиляторы K & R (по общему признанию, древние) не понимают их.

Если макрос реализован правильно (т.е. он не страдает от проблем ассоциативности при замене), то нет никакой разницы в применимости между константами макроса и enum в ситуациях, когда они применимы, т. Е. В ситуации, когда вам нужны специальные целые константы со знаком.

Однако в общем случае macros обеспечивают гораздо более гибкую функциональность. Enums накладывают определенный тип на ваши константы: они будут иметь тип int (или, возможно, более крупный тип целочисленного знака), и они всегда будут подписаны. С помощью макросов вы можете использовать постоянные синтаксисы, суффиксы и / или явные преобразования типов для создания константы любого типа.

Enums работают лучше всего, когда у вас есть группа тесно связанных последовательных целочисленных констант. Они работают особенно хорошо, когда вас вообще не волнуют фактические значения констант, т. Е. Когда вы заботитесь только о том, чтобы у них были некоторые уникальные ценности. Во всех остальных случаях macros – лучший выбор (или, в основном, единственный выбор).

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

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

 #define X 100L enum { Y = 100L }; printf("%ld\n", X); printf("%d\n", Y); /* Y has int type */