В языке C++ есть квалификаторы и спецификаторы, которые в связи с их не очень частым использованием могут вызвать замешательство у неискушённых и начинающих. В данном коротком очерке я собираюсь пролить свет на данную тему.
Начнём с квалификатора volatile
Если подходить формально то квалификатор volatile информирует компилятор что переменная может быть изменена не явным способом т.е. без явного использовать оператора присвоения. И что же это означает на практике? Дело в том, что как правило компиляторы автоматически применяют оптимизацию, предполагая что значение переменной остаётся постоянным, если оно не указано с левой стороны от оператора присваивания. Т.е. если переменная не меняется, то и нет нужды проверять её при каждом обращении.
Пример:
bool exit = true;
while( exit )
{
};
В данном случае значение переменной exit будет прочитано только один раз. На первый взгляд в этом не ничего страшного. Однако, если содержание переменно exit может изменять не только код текущего потока, но допустим другой поток (опустим сейчас вопросы атомарности операции) или вообще некое внешние устройство, то мы можем получить бесконечный цикл коим он по нашей задумке быть не должен.
Но если мы перед объявлением переменной поставим квалификатор volatile, то переменная будет считываться при каждой проверке.
Пример:
volatile bool exit = true;
while( exit )
{
};
Приведу более жизненный пример. Предположим есть необходимость работы с внешнем устройством через некоторый порт. Нам нужно записать в порт последовательность из трёх нулей.
unsigned char* pControl = 0xff24 ;
*pControl = 0 ;
*pControl = 0 ;
*pControl = 0 ;
После оптимизации вместо трёх нулей будет записан только один. Более того после оптимизации прочитать что либо из порта будет не реально т.к. изменения переменной будут происходить во внешней среде.
Герберт Шилдт приводит такой пример: Например, адрес глобальной переменной можно передать таймеру операционной системы и использовать его для отсчёта времени. В этом случае содержимое переменной изменяется без явного выполнения какого-либо оператора присвоения.
Чисто теоретически если быть стопроцентно уверенным в атомарности операции, то можно использовать квалификатор volatile для взаимодействие переменной между потоками без использования дополнительных объектов синхронизации таких как мьютексы, но поскольку такую уверенность может дать разве что char, то применять volatile в этих целях будет не корректным.
Применять volatile в дополнение к мьютексам нет необходимости, хотя на эту тему сломано много копий, но примеров реальных компиляторов и ОС, где мьютексов мало, мне не встречалось. Кстати, функции-члены тоже могут объявляться со словом volatile:
class A
{
public:
void foo() volatile;
}
В данном случае внутри foo() указатель this так же имеет атрибут volatile.
Спецификатор extern
В языках с\с++ существуют внутренние связи, внешние связи и отсутствие связей. Глобальные переменные имеют внешние связи и это позволяет получить доступ к ним из любой части программы. Если к глобальным переменным добавить спецификатор static, то глобальные переменные утратят внешние связи и будут иметь только внутренние связи, т.е. будут доступны только внутри файла, в котором они были описаны. Локальные переменные не имеют связей и поэтому доступны только внутри блока где они были описаны.
Спецификатор extern указывает, что переменная обладает внешними связями. Дело в том, что надо различать определение и объявление. Объявление указывает имя объекта и его тип, то где как определение выделяет под объект память. Таким образом можно сделать несколько объявлений объекта и только одно определение. В большинстве случаев, определение и объявление совпадают. Спецификатор extern позволяет объявить переменную без её определения т.е без выделения памяти. Используя спецификатор extern можно путём объявления обратиться к переменной, определённой в другом месте. К примеру, можно определить все глобальные переменные в одном файле, а в других файлах получать к ним доступ через объявление со спецификатором extern.
Спецификатор register
Изначально это спецификатор применялся только к переменным типа char и int, но теперь его можно применять к переменным любого типа. Данный спецификатор указывает компилятору хранить значение переменной не в памяти, а в регистре процессора. Иной трактовкой спецификатора register служит подсказка компилятору, что данный объект используется очень интенсивно. Разумеется в регистрах смогут поместиться только данные весьма ограниченного объёма, такие как int и char, а боле крупные объекты в регистры не поместятся, но получат более высокой приоритет обработки. Надо учитывать, что register это рекомендация такая же как inline и компилятор может просто игнорировать спецификатор register и обрабатывать переменную как обычно.
Так же замечу, что спецификатор register можно применять только к локальным переменным и формальным параметрам.
bool foo(register char ch)
{
register bool bRes;
return bRes;
}
Компилятор автоматически преобразует лишние регистровые переменные в обычные, поэтому нет смысла в подсчёте количества регистровых переменных что обеспечивает машино-независимость.
Ключевое слово auto
Судьбу этого ключевого слово можно сравнить с goto: с одной стороны - в языке есть, с другой - его не используют. Но в случае с auto всё проще. Хотя его и можно использовать для объявления локальных переменных, но смысла в этом нет, так как все локальные переменные по умолчанию считаются автоматическими. Поэтому на практике это ключевое слово не используется. Есть мнение что ключевое слово auto включили в язык С для совместимости с языком B ну а потом оно перекочевало и в С++
Ключевое слово mutable
Иногда есть необходимость изменить некий объект внутри класса, гарантируя неприкосновенность остальных элементов. Неприкосновенность можно гарантировать при помощи const, однако const запрещает изменение всего.
class Exm
{
int a;
int b;
public:
int getA() const
{
return a; // все правильно
}
int setA(int i) const
{
a = i;// ошибка доступа
}
}
Помочь в данном случае может определение переменной а с ключевым словом mutable. Внесём исправление в приведённый чуть выше пример:
class Exm
{
mutable int a; // добавили в объявление ключевое слово mutable
// позволяющие игнорировать модификатор const
// по отношению к данной переменной
int b;
public:
int getA() const //
{
return a; // все правильно
}
int setA(int i) const
{
a = i;// теперь всё правильно. Мы можем изменять переменную а
b = i; // Ошибка! Переменная b по прежнему не доступна для изменения.
}
}
------------
Александр Бабашов (tellurian)