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

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

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

Ответ 1

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 оптимизатор видит, что цикл бесполезен (Парень никогда не устанавливает значение! Он гаснет, избавьтесь от этого кода!), и мой код продолжится, не приобретя семафор, в результате чего возникнут проблемы.

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

Из статьи Дэна Сакса "Волатильно как обещание":

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

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

Ответ 5

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

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

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

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);

Ответ 6

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

Ответ 7

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

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

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

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

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

static volatile sig_atomic_t sig_num = 0;

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

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

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

Помимо использования по назначению, 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 памяти.

Ответ 11

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

Ответ 12

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

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

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

1) Глобальные переменные, модифицированные процедурой обслуживания прерываний вне области видимости.

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

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

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

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

Летучий: лучший друг программистов

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

Ответ 13

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

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

Ответ 14

Ваша программа работает даже без ключевого слова 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, даже если ваша программа работает без нее. Это делает намерение более ясным и надежным в отношении будущих изменений.

Ответ 15

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

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

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

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

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

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

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

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

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

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

Ответ 16

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