В языке С определяются квалификаторы типа[1], указывающие на доступность и модифицируемость переменной. Стандарт С89 определяет два квалификатора: const и volatile. (C99 добавляет третий, restrict, описанный в части II.) Квалификатор типа должен предшествовать имени типа, который он квалифицирует (уточняет).
Квалификатор const
Переменная, к которой в объявлении (декларации) применен квалификатор const, не может изменять свое значение[2]. Ее можно только инициализировать, то есть присвоить ей значение в начале выполнения программы. Компилятор может поместить переменную этого типа в постоянное запоминающее устройство, так называемое ПЗУ (ROM, read-only memory). Например, в объявлении
const int a=10;
создается переменная с именем а, причем ей присваивается начальное значение 10, которое в дальнейшем в программе изменить никак нельзя. Переменную, к которой в объявлении применен квалификатор const, можно использовать в различных выражениях. Однако свое значение она может получить только в результате инициализации или с помощью аппаратно-зависимых средств.
Квалификатор const часто используется для того, чтобы предотвратить изменение функцией объекта, на который указывает аргумент функции. Без него при передаче в функцию указателя эта функция может изменить объект, на который он указывает. Однако если в объявлении параметра-указателя применен квалификатор const, функция не сможет изменить этот объект. В следующем примере функция sp_to_dash() печатает минус вместо каждого пробела в строке, передаваемой ей как аргумент. То есть строка «тестовый пример» будет напечатана как «тестовый-пример». Применение квалификатора const в объявлении параметра функции гарантирует, что внутри функций объект, на который указывает параметр функции, не будет изменен.
#include <stdio.h>
void sp_to_dash(const char *str);
int main(void)
{
sp_to_dash("тестовый пример");
return 0;
}
void sp_to_dash(const char *str)
{
while(*str) {
if(*str== ' ') printf("%c", '-');
else printf("%c", *str);
str++;
}
}
Если написать sp_to_dash() таким образом, что внутри функции строка изменяется, то еще на этапе компиляции в программе будет обнаружена ошибка. Например, на этапе компиляции возникнет ошибка, если написать так:
/* Неправильный пример. */
void sp_to_dash(const char *str)
{
while(*str) {
if(*str==' ' ) *str = '-'; /* это не правильно */
printf("%c", *str);
str++;
}
}
Квалификатор const используется в объявлениях параметров многих функций стандартной библиотеки. Например, прототип функции strlen() выглядит так:
size_t strlen(const char *str);
Применение квалификатора const в объявлении str гарантирует, что функция не изменит строку, на которую указывает str. Если функция стандартной библиотеки не предназначена для изменения аргумента, то практически всегда в объявлении указателя на аргумент применяется квалификатор const.
Программист тоже может применять квалификатор const для того, чтобы гарантировать сохранность объекта. Но следует помнить, что переменная, даже если к ней применен квалификатор const, может быть изменена в результате какого-нибудь внешнего по отношению к программе воздействия. Например, ей может быть присвоено значение каким либо устройством. Однако применение квалификатора const в объявлении переменной гарантирует, что ее изменение может произойти только в ходе внешнего по отношению к программе события.
Квалификатор volatile
Квалификатор volatile указывает компилятору на то, что значение переменной может измениться независимо от программы, т.е. вследствие воздействия еще чего-либо, не являющегося оператором программы. Например, адрес глобальной переменной можно передать в подпрограмму операционной системы, следящей за временем, и тогда эта переменная будет содержать системное время. В этом случае значение переменной будет изменяться без участия какого-либо оператора программы. Знание таких подробностей важно потому, что большинство компиляторов С автоматически оптимизируют некоторые выражения, предполагая при этом неизменность переменной, если она не встречается в левой части оператора присваивания. В этом случае при очередной ссылке на переменную может использоваться ее предыдущее значение. Некоторые компиляторы изменяют порядок вычислений в выражениях, что может привести к ошибке, если в выражении присутствует переменная, вычисляемая вне программы. Квалификатор volatile предотвращает такие изменения программы.
Квалификаторы const и volatile могут применяться и совместно. Например, если 0×30 — адрес порта, значение в котором может задаваться только извне, то следующее объявление предотвратит всякую возможность нежелательных побочных эффектов:
const volatile char *port = (const volatile char *) 0x30;
[1]Называются также классификаторами, описателями, спецификаторами.
[2]На жаргоне программистов: переменная «типа» const не может изменять значение. В описании многих языков такие переменные часто называются константами. Но если исключить левые части операторов присваивания, то переменные этого «типа» могут использоваться в тех же ситуациях, что и «настоящие» переменные. В этом смысле константы являются частным случаем переменных.