Топ контрибуторов
loading
loading
Знаете ли Вы, что

Свои вопросы для тестов можно добавлять на странице с информацией о тесте. При этом для некоторых тестов добавление вопросов закрыто

Лента обновлений
ссылка Oct 22 23:24
Комментарий от alexcei88:
В вопросе переменная s даже не выводиться, а выводитьс...
ссылка Oct 22 17:46
Комментарий от AlexFurm:
Это UB, так можно вызывать только статические функции ч...
ссылка Oct 22 17:43
Комментарий от AlexFurm:
Любые битовые операции с signed это UB
ссылка Oct 21 20:30
Комментарий от yoori:
Любой вариант скомпилируется если компилировать не в конеч...
ссылка Oct 21 16:53
Добавлен вопрос в тест QA (Quality Assurance)
Статистика

Тестов: 153, вопросов: 8596. Пройдено: 443367 / 2177508.

Использование volatile, extern, register, auto, mutable в C++ (обзор)

head tail Статья
категория
C++
дата12.09.2009
авторtellurian
голосов46

В языке 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)

Если Вам понравилась статья, проголосуйте за нее

Голосов: 46  loading...
admin   sklym   udjin   serj   hammerbos   kyser   mlyundin   molyxp   kislotnik   mborodin   valyala   rha   MichaelN   anEcho   kozlovsergij   zipu4   ponka   Deadly   JurasicSTR   GaiveR   qwqwqw2   IchMors   Navi   ytec   sava_vodka   flenderbit   kopalvich   bugryn   Probleskovy   ksyunyaka   arcan82   Svarog17   denisgrin1618   Alexorleon   c0nst   lupus   freakymaryk   SunDrop   anoobis   dark13   Staller   ZorN   deathsun   loskamo   miryaha   alex_fibonacci