it-swarm-ru.tech

Почему летучие существуют?

Что делает ключевое слово volatile? В C++ какую проблему это решает?

В моем случае я никогда не нуждался в этом сознательно.

194
theschmitzer

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

Раньше я работал с двухпортовым ОЗУ в многопроцессорной системе на прямом C. Мы использовали 16-битное значение с аппаратным управлением как семафор, чтобы знать, когда другой парень закончил. По сути, мы сделали это:

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

Без volatile оптимизатор считает цикл бесполезным (парень никогда не устанавливает значение! Он с ума сошел, избавьтесь от этого кода!), И мой код продолжит работу, не получив семафор, что впоследствии вызовет проблемы.

240
Doug T.

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

77
ChrisN

Некоторые процессоры имеют регистры с плавающей запятой, которые имеют точность более 64 бит (например, 32-битный x86 без SSE, см. Комментарий Питера). Таким образом, если вы выполняете несколько операций над числами с двойной точностью, вы на самом деле получаете ответ с более высокой точностью, чем если бы вы усекали каждый промежуточный результат до 64 бит.

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

Это также полезно для некоторых алгоритмов, которые не имеют алгебраического смысла, но уменьшают ошибку с плавающей запятой, например, суммирование по Кахану. Алгебраически это не nop, поэтому он часто будет неправильно оптимизирован, если некоторые промежуточные переменные не являются изменчивыми.

68
tfinniga

Из статьи Дэна Сакса "Летучий как обещание" :

(...) изменчивый объект - это объект, значение которого может меняться самопроизвольно. То есть, когда вы объявляете объект энергозависимым, вы сообщаете компилятору, что объект может изменить состояние, даже если никакие операторы в программе не изменяют его ".

Вот ссылки на три его статьи относительно ключевого слова volatile:

44
MikeZ

Вы ДОЛЖНЫ использовать volatile при реализации структур данных без блокировки. В противном случае компилятор может оптимизировать доступ к переменной, что изменит семантику.

Иными словами, volatile сообщает компилятору, что доступ к этой переменной должен соответствовать операции чтения/записи в физической памяти.

Например, вот как InterlockedIncrement объявляется в Win32 API:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);
23
Frederik Slijkerman

Большое приложение, над которым я работал в начале 1990-х, содержало обработку исключений на основе C с использованием setjmp и longjmp. Ключевое слово volatile было необходимо для переменных, чьи значения необходимо было сохранить в блоке кода, который служил в качестве предложения "catch", чтобы эти переменные не сохранялись в регистрах и не уничтожались longjmp.

10
Jeff Doar

В стандарте C одно из мест, где можно использовать volatile, - это обработчик сигнала. На самом деле, в стандарте C все, что вы можете безопасно сделать в обработчике сигналов, это изменить переменную volatile sig_atomic_t или быстро выйти. Действительно, AFAIK, это единственное место в стандарте C, где использование volatile необходимо, чтобы избежать неопределенного поведения.

ISO/IEC 9899: 2011 §7.14.1.1 Функция signal

If5 Если сигнал возникает не в результате вызова функции abort или raise, поведение не определено, если обработчик сигнала ссылается на какой-либо объект со статическим или потоковым сроком хранения, который не является атомарным объектом без блокировки, кроме как путем назначения значение объекта, объявленного как volatile sig_atomic_t, или обработчик сигнала вызывает любую функцию в стандартной библиотеке, кроме функции abort, функции _Exit, функции quick_exit или signal, причем первый аргумент равен номеру сигнала, соответствующему сигнал, вызвавший вызов обработчика. Кроме того, если такой вызов функции signal приводит к возвращению SIG_ERR, значение errno является неопределенным.252)

252) Если какой-либо сигнал генерируется асинхронным обработчиком сигнала, поведение не определено.

Это означает, что в стандарте C вы можете написать:

static volatile sig_atomic_t sig_num = 0;

static void sig_handler(int signum)
{
    signal(signum, sig_handler);
    sig_num = signum;
}

и не намного больше.

POSIX гораздо более снисходительно относится к тому, что вы можете делать в обработчике сигналов, но все еще существуют ограничения (и одно из ограничений заключается в том, что стандартную библиотеку ввода-вывода - printf() и др. - нельзя использовать безопасно).

10
Jonathan Leffler

Помимо использования по назначению, volatile используется в (шаблонном) метапрограммировании. Его можно использовать для предотвращения случайной перегрузки, поскольку атрибут volatile (например, const) участвует в разрешении перегрузки.

template <typename T> 
class Foo {
  std::enable_if_t<sizeof(T)==4, void> f(T& t) 
  { std::cout << 1 << t; }
  void f(T volatile& t) 
  { std::cout << 2 << const_cast<T&>(t); }

  void bar() { T t; f(t); }
};

Это законно; обе перегрузки потенциально могут быть вызваны и почти одинаковы. Приведение в перегрузке volatile допустимо, так как мы знаем, что бар в любом случае не пропустит энергонезависимое T. Версия volatile строго хуже, однако, поэтому никогда не выбирается в разрешении перегрузки, если доступно энергонезависимое f.

Обратите внимание, что код никогда не зависит от доступа к памяти volatile.

7
MSalters

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

7
indentation

Разрабатывая для встроенного, у меня есть цикл, который проверяет переменную, которая может быть изменена в обработчике прерываний. Без "volatile" цикл превращается в петлю - насколько может сказать компилятор, переменная никогда не меняется, поэтому она оптимизирует проверку.

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

7
Arkadiy
  1. вы должны использовать его для реализации спин-блокировок, а также некоторых (всех?) структур данных без блокировки
  2. используйте его с атомарными операциями/инструкциями
  3. помог мне однажды преодолеть ошибку компилятора (неправильно сгенерированный код во время оптимизации)
6
Mladen Janković

Ключевое слово volatile предназначено для предотвращения применения компилятором каких-либо оптимизаций к объектам, которые могут изменяться способами, которые не могут быть определены компилятором.

Объекты, объявленные как volatile, не включаются в оптимизацию, поскольку их значения могут быть изменены кодом за пределами области действия текущего кода в любое время. Система всегда считывает текущее значение объекта volatile из памяти, а не сохраняет его значение во временном регистре в той точке, в которой оно запрашивается, даже если предыдущая инструкция запрашивала значение из того же объекта.

Рассмотрим следующие случаи

1) Глобальные переменные, измененные подпрограммой обработки прерываний вне области действия.

2) Глобальные переменные в многопоточном приложении.

Если мы не используем volatile квалификатор, могут возникнуть следующие проблемы

1) Код может не работать должным образом при включенной оптимизации.

2) Код может работать не так, как ожидалось, когда прерывания включены и используются.

Изменчивый: лучший друг программиста

https://en.wikipedia.org/wiki/Volatile_ (computer_programming)

4
roottraveller

Ваша программа работает даже без ключевого слова volatile? Возможно, это причина

Как упоминалось ранее, ключевое слово volatile помогает в таких случаях, как

volatile int* p = ...;  // point to some memory
while( *p!=0 ) {}  // loop until the memory becomes zero

Но, похоже, эффект почти не проявляется после вызова внешней или не встроенной функции. Например.:

while( *p!=0 ) { g(); }

Затем с или без volatile генерируется почти одинаковый результат.

Пока g() может быть полностью встроенным, компилятор может видеть все, что происходит, и поэтому может оптимизировать. Но когда программа обращается к месту, где компилятор не может видеть, что происходит, компилятору небезопасно делать какие-либо предположения. Следовательно, компилятор будет генерировать код, который всегда читает непосредственно из памяти.

Но остерегайтесь того дня, когда ваша функция g() станет встроенной (либо из-за явных изменений, либо из-за хитрости компилятора/компоновщика), тогда ваш код может сломаться, если вы забудете ключевое слово volatile!

Поэтому я рекомендую добавить ключевое слово volatile, даже если ваша программа работает без него. Это делает намерение более ясным и надежным в отношении будущих изменений.

2
Joachim

В первые дни C компиляторы интерпретировали все действия, которые читают и записывают значения l, как операции с памятью, которые должны выполняться в той же последовательности, в которой операции чтения и записи появились в коде. Во многих случаях эффективность могла бы быть значительно улучшена, если бы компиляторам была предоставлена ​​определенная свобода для переупорядочения и консолидации операций, но с этим была проблема. Даже операции часто указывались в определенном порядке только потому, что необходимо было указывать их в некоторых порядке, и, таким образом, программист выбрал одну из многих одинаково хороших альтернатив, что было не всегда так. Иногда было бы важно, чтобы определенные операции происходили в определенной последовательности.

Какие именно детали последовательности важны, будет зависеть от целевой платформы и области применения. Вместо того, чтобы предоставлять особо подробный контроль, Стандарт выбрал простую модель: если последовательность обращений выполняется с l-значениями, которые не определены volatile, компилятор может переупорядочить и объединить их, как он считает нужным. Если действие выполняется с volatile- квалифицированным lvalue, качественная реализация должна предлагать любые дополнительные гарантии упорядочения, которые могут потребоваться при коде, нацеленном на его предполагаемую платформу и область приложения, без необходимости использования нестандартного синтаксиса.

К сожалению, вместо того, чтобы определить, какие гарантии понадобятся программистам, многие компиляторы предпочли вместо этого предложить минимальные гарантии, предусмотренные стандартом. Это делает volatile гораздо менее полезным, чем должно быть. Например, в gcc или clang программист, которому нужно реализовать базовый "мьютекс передачи" [тот, в котором задача, которая получила и освободил мьютекс, не будет делать это до тех пор, пока другая задача не выполнит это], должен выполнить один из четырех вещей:

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

  2. Определите все объекты, защищенные мьютексом, как volatile-- что-то, что не должно быть необходимым, если все обращения происходят после получения мьютекса и перед его освобождением.

  3. Используйте уровень оптимизации 0, чтобы заставить компилятор генерировать код, как если бы все объекты, которые не определены register, были volatile.

  4. Используйте специфичные для gcc директивы.

Напротив, при использовании более качественного компилятора, который больше подходит для системного программирования, такого как icc, можно было бы выбрать другой вариант:

  1. Убедитесь, что volatile- квалифицированная запись выполняется везде, где требуется приобретение или выпуск.

Для получения базового "мьютекса передачи обслуживания" требуется чтение volatile (чтобы узнать, готово ли оно), и не требуется также запись volatile (другая сторона не будет пытаться повторно получить ее, пока она не будет возвращена), но необходимость выполнять бессмысленную запись volatile все же лучше, чем любая из опций, доступных в gcc или clang.

2
supercat

Помимо того, что ключевое слово volatile используется для указания компилятору не оптимизировать доступ к некоторой переменной (которая может быть изменена потоком или подпрограммой прерывания), оно также может быть используется для удаления некоторых ошибок компилятора - ДА, это может быть ---.

Например, я работал над встроенной платформой, где компилятор делал некоторые неверные предположения относительно значения переменной. Если код не был оптимизирован, программа будет работать нормально. С оптимизацией (которая была действительно необходима, потому что это была критическая процедура) код не работал бы правильно. Единственное решение (хотя и не очень правильное) состояло в том, чтобы объявить переменную "неисправный" как volatile.

2
INS

Я должен напомнить вам, что в функции-обработчике сигналов вы можете использовать глобальную переменную (например, пометить ее как exit = true), если вы хотите объявить эту переменную как volatile.

1
bugs king