Menu

Константы в C: const, #define и enum

Три способа дать имя значению, которое никогда не меняется, — ключевое слово const, макросы #define и константы enum: чем каждый из них на самом деле является и когда какой выбрать.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Константа — это значение с именем и обещанием, что оно не изменится. C предлагает для этого три механизма, и внутри они устроены совершенно по-разному: один — это переменная, другой — текстовая подстановка, третий — целочисленный тип.

Зачем вообще давать значению имя

Сравните две строки:

double area = 3.14159 * r * r;
double area = PI * r * r;

Вторая лучше по трём причинам, и все они проявятся позже. Она говорит, что означает число. Оно встречается ровно в одном месте, поэтому изменить точность — это одна правка, а не поиск по всему коду. И опечатка в PI — ошибка компиляции, тогда как опечатка в 3.14195 — просто неправильный ответ.

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

const

Ключевое слово const помечает переменную как доступную только для чтения:

Раскомментируйте присваивание — и компилятор откажется: assignment of read-only variable 'PI'. Эта ошибка на этапе сборки и есть весь смысл.

Переменная const — настоящая переменная. У неё есть тип, поэтому компилятор проверяет, как вы её используете. Она подчиняется области видимости, так что const внутри функции наружу не утекает. У неё есть адрес, поэтому на неё можно взять указатель. И отладчик может показать её имя и значение.

const обязана быть инициализирована прямо в объявлении — второго шанса не будет:

const int limit;       /* ошибка (или мусор навсегда): присвоить ей позже уже ничего нельзя */
const int limit = 50;  /* правильно */

#define

Альтернатива от препроцессора — макрос:

Это не переменная. Ещё до запуска компилятора препроцессор заменяет каждое вхождение PI в исходнике на символы 3.14159265358979. Компилятор вообще не видит этого имени — именно поэтому у #define нет типа и он не может появиться в отладчике.

Обратите внимание: ни точки с запятой, ни =. Макрос — это сырой текст, поэтому вот такая частая ошибка:

#define MAX_USERS 100;        /* обратите внимание на точку с запятой */

int limit = MAX_USERS + 1;    /* разворачивается в: int limit = 100; + 1;  -> ошибка */

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

Макросы по традиции пишут в UPPER_CASE; переменные const обычно следуют тому же стилю, когда выражают ту же идею.

Константы enum

Третья форма даёт вам набор именованных целых чисел:

Значения начинаются с 0 и растут на единицу, если вы не задали их явно, поэтому MON равно 0, а FRI — 4. Анонимный enum { ... } — распространённая идиома для обычных целочисленных констант.

У констант enum есть реальное преимущество перед обеими альтернативами: это целочисленные константы времени компиляции, понятные компилятору, и они видны отладчику. Подробный рассказ — на странице про перечисления в C.

Что выбрать

Что нужноЧто использовать
Типизированная константа с областью видимости, видимая в отладчикеconst
Размер массива, метка case, ширина битового поляenum или #define
Строковая константа#define или const char *
Константа с плавающей точкойconst double
Набор связанных целочисленных значенийenum
Всё, что должен видеть препроцессор (#if, защита от повторного включения)#define

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

По-настоящему это бьёт в одном месте — на размерах массивов:

const int в C — это переменная только для чтения, а не константное выражение времени компиляции. Внутри функции C99 принимает int local[const_size], делая его массивом переменной длины; на уровне файла, в struct или в качестве метки case он отвергается сразу. enum и #define работают везде.

(Это одно из реальных отличий от C++, где const int является константным выражением. Код, переезжающий между языками, на этом спотыкается.)

const и указатели

const и указатели сочетаются тремя способами, и смыслы у них разные. Читайте объявление от имени наружу:

const char *p;          /* p указывает на символы, которые нельзя изменять */
char * const q;         /* сам q нельзя перенаправить; символы менять можно */
const char * const r;   /* нельзя менять ни указатель, ни символы */

Форму const char * вы будете писать чаще всего, потому что это правильный тип для параметра функции, которая читает строку, не изменяя её:

Добавление const к параметру, доступному только для чтения, ничего не стоит и документирует контракт так, что компилятор его соблюдает. Это стоит делать везде.

Строковые литералы фактически константны

Тонкая ловушка, ради которой const и существует:

char *s = "hello";   /* легальный C, но s указывает в память только для чтения */
s[0] = 'H';          /* неопределённое поведение - обычно падение */

Строковые литералы живут в секции исполняемого файла, доступной только для чтения. Запись через такой указатель обычно приводит к segfault. Объявление указателя как const char *s = "hello"; заставляет компилятор отвергнуть присваивание, вместо того чтобы дать программе упасть во время выполнения.

Если нужна изменяемая строка, скопируйте её в массив:

Часто задаваемые вопросы

Как объявить константу в C?

Добавьте const в объявление и сразу задайте значение: const double PI = 3.14159;. После этого компилятор отвергнет любую попытку присвоить ей что-то новое. Более старая альтернатива — макрос препроцессора #define PI 3.14159, который вместо этого выполняет текстовую подстановку.

В чём разница между const и #define в C?

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

Можно ли использовать const-переменную как размер массива в C?

Для массива фиксированного размера на уровне файла — нет. const int N = 10; int arr[N]; — это массив переменной длины из C99 (допустим внутри функции, но не на уровне файла и не в структуре). Для настоящего размера времени компиляции используйте #define N 10 или enum { N = 10 };.

Что означает const char *p?

Это указатель на константный char: менять то, куда указывает p, можно, а сам символ по этому адресу — нет. char * const p — противоположность: константный указатель на изменяемый char. const char * const p фиксирует и то, и другое. Читайте объявление от имени наружу.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ