«Static const» против «#define» против «enum»

531

Какой из них лучше использовать среди приведенных ниже инструкций в C?

static const int var = 5;

или

#define var 5

или

enum { var = 5 };
  • 29
    Интересно, что это почти тот же вопрос, что и stackoverflow.com/questions/1637332/static-const-vs-define . Единственное отличие состоит в том, что этот вопрос касается C ++, а этот вопрос касается C. Поскольку мой ответ был специфичным для C ++, я говорю, что они не идентичны, но другие могут не согласиться.
  • 45
    Не идентично, определенно. Существует множество областей, где C ++ допускает синтаксис C по соображениям совместимости. В таких случаях такие вопросы, как «как лучше всего сделать X», будут иметь разные ответы в C ++. Например, инициализация объекта.
Показать ещё 2 комментария
Теги:
constants

18 ответов

275
Лучший ответ

Вообще говоря:

static const

Поскольку он уважает область действия и безопасен по типу.

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

#ifdef VAR // Very bad name, not long enough, too general, etc..
  static int const var = VAR;
#else
  static int const var = 5; // default value
#endif

Если возможно, вместо макроса/эллипса используйте альтернативу типа.

Если вам действительно нужно пойти с макросом (например, вы хотите __FILE__ или __LINE__), то лучше назовите свой макрос ОЧЕНЬ тщательно: в соглашение об именах Boost рекомендует весь верхний регистр, начиная с названия проекта (здесь BOOST_), просматривая библиотеку, вы заметите, что это (обычно), за которым следует имя конкретной области (библиотеки), затем с содержательным именем.

Обычно это относится к длительным именам:)

  • 2
    Согласен - также с #define существует общая опасность искажения кода, поскольку препроцессор не знает синтаксиса.
  • 9
    Лучше использовать #if, чем #ifdef, но в остальном я согласен. +1.
Показать ещё 12 комментариев
611

Это зависит от того, для чего вам нужно значение. Вы (и все остальные до сих пор) опустили третий вариант:

  • static const int var = 5;
  • #define var 5
  • enum { var = 5 };

Игнорируя проблемы выбора имени, затем:

  • Если вам нужно передать указатель, вы должны использовать (1).
  • Так как (2), по-видимому, является опцией, вам не нужно передавать указатели.
  • Оба (1) и (3) имеют символ в таблице символов отладчика, что упрощает отладку. Скорее всего, что (2) не будет иметь символа, оставляя вас интересно, что это такое.
  • (1) не может использоваться как измерение для массивов в глобальном масштабе; оба (2) и (3) могут.
  • (1) не может использоваться как измерение для статических массивов в области функций; оба (2) и (3) могут.
  • В C99 все они могут использоваться для локальных массивов. Технически, использование (1) подразумевало бы использование VLA (массив переменной длины), хотя размер, на который ссылается "var", конечно, был бы зафиксирован в размере 5.
  • (1) не может использоваться в таких местах, как команды switch; оба (2) и (3) могут.
  • (1) не может использоваться для инициализации статических переменных; оба (2) и (3) могут.
  • (2) может изменить код, который вы не хотите изменять, потому что он используется препроцессором; оба (1) и (3) не будут иметь неожиданных побочных эффектов.
  • Вы можете определить, было ли (2) установлено в препроцессоре; ни (1), ни (3) не допускают этого.

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

Если вы спрашивали о С++, тогда вы будете использовать параметр (1) - статический const - каждый раз.

  • 101
    фантастический список! Недостатком enum является то, что они реализованы как int ([C99] 6.7.2.2/3). #define позволяет указывать unsigned и long с суффиксами U и L , а const позволяет указывать тип. enum может вызвать проблемы с обычными преобразованиями типов.
  • 28
    (2) люди ВСЕГДА жалуются на безопасность типов. Я никогда не понимаю, почему бы просто не использовать «#define var ((int) 5)» и ура, вы получили безопасность типов с определением.
Показать ещё 21 комментарий
93

В С, в частности? В C правильный ответ: используйте #define (или, при необходимости, enum)

В то время как полезно иметь свойства определения области охвата и типирования объекта const, в действительности const объекты в C (в отличие от С++) не являются истинными константами и поэтому обычно являются бесполезными в большинстве практических случаев.

Итак, в C выбор должен определяться тем, как вы планируете использовать свою константу. Например, вы не можете использовать объект const int в качестве метки case (в то время как макрос будет работать). Вы не можете использовать объект const int в качестве ширины битового поля (в то время как макрос будет работать). В C89/90 вы не можете использовать объект const для указания размера массива (в то время как макрос будет работать). Даже в C99 вы не можете использовать объект const для указания размера массива, когда вам нужен массив

  • 6
    «Вы не можете использовать объект const int в качестве метки регистра (в то время как макрос будет работать)» ---> Что касается этого утверждения, я протестировал переменную const int в C в switch-case, он работает ....
  • 8
    @john: Ну, вам нужно предоставить код, который вы тестировали, и назвать конкретный компилятор. Использование объектов const int в case-метках запрещено во всех версиях языка Си. (Разумеется, ваш компилятор может поддерживать его как нестандартное расширение языка C ++.)
Показать ещё 3 комментария
29

Разница между static const и #define заключается в том, что первая использует память, а позже не использует память для хранения. Во-вторых, вы не можете передать адрес #define, тогда как вы можете передать адрес static const. На самом деле это зависит от того, какое обстоятельство мы находимся, нам нужно выбрать один из этих двух. Оба они в своих лучших проявлениях при разных обстоятельствах. Пожалуйста, не предполагайте, что один лучше другого...: -)

Если бы это было так, Деннис Ричи сохранил бы лучший один один... hahaha...: -)

  • 6
    +1 за упоминание памяти, некоторые встроенные системы все еще не имеют так много, хотя я бы, вероятно, начал с использования статических констант и изменил бы на #defines только при необходимости.
  • 3
    Я только что проверил это. Действительно, const int использует дополнительную память по сравнению с #define или enum. Поскольку мы программируем встроенные системы, мы не можем позволить себе дополнительное использование памяти. Итак, мы вернемся к использованию #define или enum.
Показать ещё 1 комментарий
14

В C #define гораздо более популярна. Вы можете использовать эти значения для объявления размеров массива, например:

#define MAXLEN 5

void foo(void) {
   int bar[MAXLEN];
}

ANSI C не позволяет использовать static const в этом контексте, насколько я знаю. В С++ вы должны избегать макросов в этих случаях. Вы можете написать

const int maxlen = 5;

void foo() {
   int bar[maxlen];
}

и даже оставить вне static, потому что внутренняя привязка подразумевается const уже [только в С++].

  • 1
    Что вы имеете в виду под "внутренней связью"? Я могу иметь const int MY_CONSTANT = 5; в один файл и получить к нему доступ extern const int MY_CONSTANT; в другой. Я не мог найти любую информацию в стандарте (C99 , по крайней мере) о const изменения поведения по умолчанию «6.2.2: 5 Если объявление в идентифицируемом эр для объекта имеет фи ль сфера применения и не хранение класса специфического эр, его связь является внешним» ,
  • 0
    @ Gauthier: Извините, об этом. Я должен был сказать «подразумевается const уже в языке C ++». Это специфично для C ++.
Показать ещё 2 комментария
13

Другим недостатком const в C является то, что вы не можете использовать значение при инициализации другого const.

static int const NUMBER_OF_FINGERS_PER_HAND = 5;
static int const NUMBER_OF_HANDS = 2;

// initializer element is not constant, this does not work.
static int const NUMBER_OF_FINGERS = NUMBER_OF_FINGERS_PER_HAND 
                                     * NUMBER_OF_HANDS;

Даже это не работает с константой, поскольку компилятор не видит ее как константу:

static uint8_t const ARRAY_SIZE = 16;
static int8_t const lookup_table[ARRAY_SIZE] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // ARRAY_SIZE not a constant!

Я был бы счастлив использовать типизированные const в этих случаях, в противном случае...

  • 4
    Немного опоздал к игре, но этот вопрос возник в другом вопросе. Погоня, почему ваш static uint8_t const ARRAY_SIZE = 16; внезапно перестал компилироваться может быть немного сложно, особенно когда #define ARRAY_SIZE 256 десять уровней в запутанной паутине заголовков. Что все заглавные буквы ARRAY_SIZE на неприятности. Зарезервируйте ALL_CAPS для макросов и никогда не определяйте макрос, который не находится в форме ALL_CAPS.
  • 0
    @ Дэвид: здравый совет, которому я буду следовать.
Показать ещё 1 комментарий
9

Если вам это удастся, static const имеет много преимуществ. Он подчиняется нормальным принципам видимости, видимым в отладчике и обычно подчиняется правилам, которым подчиняются переменные.

Однако, по крайней мере, в исходном стандарте C он фактически не является константой. Если вы используете #define var 5, вы можете написать int foo[var]; как объявление, но вы не можете этого сделать (кроме как расширение компилятора "с static const int var = 5;. Это не так в С++, где версия static const может использоваться везде, где может быть версия #define, и я считаю, что это также относится к C99.

Однако никогда не называйте константу #define с нижним регистром. Он будет отменять любое возможное использование этого имени до конца единицы перевода. Макро-константы должны быть в том, что фактически является их собственным пространством имен, которое традиционно является прописными буквами, возможно, с префиксом.

  • 6
    К сожалению, это не так с C99. const в C99 все еще не является настоящей константой. Вы можете объявить размер массива с помощью const в C99, но только потому, что C99 поддерживает массивы переменной длины. По этой причине он будет работать только там, где разрешены VLA. Например, даже в C99 вы все еще не можете использовать const для объявления размера массива-члена в struct .
  • 0
    Хотя это верно, что C99 не позволит вам сделать это, GCC (протестированный с 4.5.3) прекрасно позволит вам инициализировать массивы с размером const int как если бы это был C ++ const или макрос. Если вы хотите зависеть от этого отклонения GCC от стандарта, это, конечно, ваш выбор, я бы лично согласился с этим, если вы не можете на самом деле предвидеть использование другого компилятора, кроме GCC или Clang, последний обладает той же функцией здесь (протестировано с Clang). 3.7).
6

Я написал программу быстрого тестирования, чтобы продемонстрировать одну разницу:

#include <stdio.h>

enum {ENUM_DEFINED=16};
enum {ENUM_DEFINED=32};

#define DEFINED_DEFINED 16
#define DEFINED_DEFINED 32

int main(int argc, char *argv[]) {

   printf("%d, %d\n", DEFINED_DEFINED, ENUM_DEFINED);

   return(0);
}

Скомпилируется с этими ошибками и предупреждениями:

main.c:6:7: error: redefinition of enumerator 'ENUM_DEFINED'
enum {ENUM_DEFINED=32};
      ^
main.c:5:7: note: previous definition is here
enum {ENUM_DEFINED=16};
      ^
main.c:9:9: warning: 'DEFINED_DEFINED' macro redefined [-Wmacro-redefined]
#define DEFINED_DEFINED 32
        ^
main.c:8:9: note: previous definition is here
#define DEFINED_DEFINED 16
        ^

Обратите внимание, что перечисление дает ошибку, когда define дает предупреждение.

5

ВСЕГДА предпочтительнее использовать const, а не #define. Это потому, что const обрабатывается компилятором и #define препроцессором. Это как #define сам не является частью кода (грубо говоря).

Пример:

#define PI 3.1416

Символьное имя PI никогда не будет видно компиляторам; он может быть удален препроцессором до того, как исходный код даже попадет в компилятор. В результате имя PI не может быть введено в таблицу символов. Это может сбивать с толку, если вы получаете ошибку во время компиляции с использованием константы, потому что сообщение об ошибке может ссылаться на 3.1416, а не на PI. Если PI были определены в заголовочном файле, который вы не писали, вы не знаете, откуда этот 3.1416.

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

Решение:

const double PI = 3.1416; //or static const...
5

#define var 5 вызовет проблемы, если у вас есть такие вещи, как mystruct.var.

Например,

struct mystruct {
    int var;
};

#define var 5

int main() {
    struct mystruct foo;
    foo.var = 1;
    return 0;
}

Препроцессор заменит его, и код не будет компилироваться. По этой причине традиционный стиль кодирования предполагает, что все константы #define используют заглавные буквы, чтобы избежать конфликтов.

4

Определение

const int const_value = 5;

не всегда определяет постоянное значение. Некоторые компиляторы (например tcc 0.9.26) просто выделяют память, идентифицированную с именем "const_value". Используя идентификатор "const_value", вы не можете изменить эту память. Но вы все еще можете изменить память с помощью другого идентификатора:

const int const_value = 5;
int *mutable_value = (int*) &const_value;
*mutable_value = 3;
printf("%i", const_value); // The output may be 5 or 3, depending on the compiler.

Это означает, что определение

#define CONST_VALUE 5

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

  • 7
    Изменение постоянного значения с помощью указателя - неопределенное поведение. Если вы хотите пойти туда, #define также можно изменить, отредактировав машинный код.
  • 0
    Ты отчасти прав. Я протестировал код с Visual Studio 2012, и он печатает 5 . Но нельзя изменить #define потому что это макрос препроцессора. Это не существует в двоичной программе. Если кто-то хотел изменить все места, где использовался CONST_VALUE , он должен был сделать это одно за другим.
Показать ещё 3 комментария
3

Кстати, альтернатива #define, которая обеспечивает правильное определение области, но ведет себя как "реальная" константа, является "перечислением". Например:

enum {number_ten = 10;}

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

Одно из важных предостережений с этим, однако: в С++ перечисленные типы имеют ограниченную совместимость с целыми числами. Например, по умолчанию невозможно выполнить арифметику. Я считаю, что это любопытное поведение по умолчанию для перечислений; в то время как было бы неплохо иметь тип "строгого перечисления", учитывая желание иметь С++ в целом совместимый с C, я думаю, что поведение по умолчанию типа "enum" должно быть взаимозаменяемым с целыми числами.

  • 0
    В C константы перечисления всегда имеют тип int , поэтому «enum hack» не может использоваться с другими целочисленными типами. (Тип перечисления совместит с некоторым определяемой реализацией целого типа, не обязательно int , но в данном случае типа является анонимным , так что не имеет значения.)
  • 0
    @KeithThompson: Так как я написал выше, я прочитал, что MISRA-C будет кричать, если компилятор назначит тип, отличный от int переменной с типом перечисления (какие компиляторы могут это делать), и каждый пытается присвоить такой переменная член собственного перечисления. Хотелось бы, чтобы комитеты по стандартам добавили переносимые способы объявления целочисленных типов с заданной семантикой. ЛЮБАЯ платформа, независимо от размера char , должна иметь возможность, например, объявлять тип, который обернет мод 65536, даже если компилятор должен добавить множество AND R0,#0xFFFF или эквивалентных инструкций.
Показать ещё 2 комментария
3

Не думайте, что есть ответ на "что всегда лучше", но, как сказал Маттие

static const

безопасен по типу. Мое самое большое домашнее животное с #define, однако, заключается в отладке Visual Studio, вы не можете смотреть эту переменную. Это дает ошибку, что символ не может быть найден.

  • 0
    «Вы не можете смотреть переменную» Верно, это не переменная. Это не меняется, зачем тебе это смотреть? Вы можете найти везде, где он используется, просто ища ярлык. Зачем вам нужно (или даже хотеть) смотреть #define?
2

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

Что касается целых чисел, если вы находитесь во встроенной среде с очень ограниченной памятью, вам может потребоваться рассказать о том, где хранится константа и как скомпилированы ее обращения к ней. Компилятор может добавить две константы во время выполнения, но добавьте два #defines во время компиляции. Константа #define может быть преобразована в одну или несколько команд MOV [немедленного], что означает, что константа эффективно сохраняется в памяти программы. Константная константа будет храниться в секции .const в памяти данных. В системах с архитектурой Гарварда могут быть различия в производительности и использовании памяти, хотя они, вероятно, будут небольшими. Они могут иметь значение для жесткой оптимизации внутренних циклов.

1

Простая разница:

При времени предварительной обработки константа заменяется на ее значение. Таким образом, вы не можете применить оператор разыменования к определению, но вы можете применить оператор разыменования к переменной.

Как вы могли предположить, быстрее определить статический const.

Например, имея:

#define mymax 100

вы не можете сделать printf("address of constant is %p",&mymax);.

Но имея

const int mymax_var=100

вы можете сделать printf("address of constant is %p",&mymax_var);.

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

Однако для static const у нас есть переменная, которая где-то выделена. Для gcc статические константы выделяются в текстовом сегменте программы.

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

  • 1
    Ваш ответ очень неправильный. Речь идет о C, ваш ответ относится к C ++, который имеет очень разную семантику для квалификатора const . C не имеет символьных констант, кроме перечислимых . const int - это переменная. Вы также путаете язык и конкретные реализации. Там нет требования, где разместить объект. И это не верно даже для GCC: как правило , он помещает const квалифицированные переменные в .rodata раздела. Но это зависит от целевой платформы. И вы имеете в виду адрес оператора & .
0

Я не уверен, прав ли я, но, по моему мнению, вызов значения #define d намного быстрее, чем вызов любой другой обычно объявленной переменной (или константного значения). Это потому, что когда программа работает и ей нужно использовать некоторую обычно объявленную переменную, ей нужно перейти в точное место в памяти, чтобы получить эту переменную.

Напротив, когда используется значение #define d, программе не нужно переходить к какой-либо выделенной памяти, она просто принимает значение. Если #define myValue 7 и программа, вызывающая myValue, она ведет себя точно так же, как когда она просто вызывает 7.

0

Я видел метод #define, используемый для управления областью константы, таким образом имитируя переменную private class в С++.

#define MAXL          4
// some code
#undef MAXL
0

Мы рассмотрели созданный код ассемблера на MBF16X... Оба варианта приводят к одному и тому же коду для арифметических операций (например, ADD Immediate).

Так что const int является предпочтительным для проверки типа, тогда как #define является старым. Возможно, он специфичен для компилятора. Поэтому проверьте полученный код ассемблера.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню