Почему существует летучесть?
Что делает ключевое слово volatile
? В C ++ какая проблема решена?
В моем случае я никогда в этом не нуждался.
- Иллюстрирование использования ключевого слова volatile в C #
- ключевое слово volatile в языке C?
- Volatile boolean vs AtomicBoolean
- Для чего используется ключевое слово «volatile»?
- Необходимость волатильного модификатора в двойной проверенной блокировке в .NET.
- Что означает «изменчивость» в Java?
- Неправильное ключевое слово C ++ вовлекает забор памяти?
- Летучие в C ++ 11
- Когда предпочтительнее использовать volatile boolean в Java, а не AtomicBoolean?
- Почему в C требуется летучесть?
- Когда вы используете ключевое слово volatile в Java?
- Неустойчивые гарантии и исполнение вне порядка
- Работа __asm__ __volatile__ (""::: "memory")
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
оптимизатор видит, что петля бесполезна (Парень никогда не устанавливает значение! Он орехи, избавиться от этого кода!), И мой код продолжится, не приобретя семафор, что вызовет проблемы позже.
volatile
необходима при разработке встроенных систем или драйверов устройств, где вам необходимо прочитать или записать аппаратное устройство с отображением памяти. Содержимое конкретного регистра устройства может измениться в любое время, поэтому вам потребуется ключевое слово volatile
чтобы гарантировать, что такой доступ не будет оптимизирован компилятором.
Большинство современных процессоров имеют регистры с плавающей запятой, которые имеют более 64 бит точности. Таким образом, если вы выполняете несколько операций с номерами с двойной точностью, вы фактически получаете ответ с более высокой точностью, чем если бы вы урезали каждый промежуточный результат до 64 бит.
Обычно это замечательно, но это означает, что в зависимости от того, как компилятор назначил регистры и сделал оптимизацию, у вас будут разные результаты для одних и тех же операций на одних и тех же входах. Если вам нужна согласованность, вы можете заставить каждую операцию вернуться в память с помощью ключевого слова volatile.
Это также полезно для некоторых алгоритмов, которые не имеют алгебраического смысла, но уменьшают ошибку с плавающей запятой, например суммирование Кахана. Алгебраически это nop, поэтому он часто будет неправильно оптимизирован, если некоторые промежуточные переменные нестабильны.
Из статьи встраиваемых систем Дэн Сакс:
«Волатильный объект – это объект, значение которого может изменяться спонтанно. То есть, когда вы объявляете объект изменчивым, вы сообщаете компилятору, что объект может измениться, даже если никакие операторы в программе не изменят его».
Ссылки на 2 замечательные статьи г-на Сакса относительно ключевого слова volatile:
http://www.embedded.com/columns/programmingpointers/174300478 http://www.embedded.com/columns/programmingpointers/175801310
Вы должны использовать volatile при внедрении незакрепленных структур данных. В противном случае компилятор может оптимизировать доступ к переменной, которая изменит семантику.
Другими словами, volatile сообщает компилятору, что доступ к этой переменной должен соответствовать операции чтения / записи физической памяти.
Например, так InterlockedIncrement объявляется в Win32 API:
LONG __cdecl InterlockedIncrement( __inout LONG volatile *Addend );
В стандарте C одним из мест для использования volatile
является обработчик сигнала. Фактически, в стандарте C все, что вы можете безопасно сделать в обработчике сигнала, – изменить volatile sig_atomic_t
переменную volatile sig_atomic_t
или быстро выйти. Действительно, AFAIK, это единственное место в стандарте C, которое требует использования volatile
чтобы избежать неопределенного поведения.
ISO / IEC 9899: 2011 §7.14.1.1.
signal
функция¶5 Если сигнал встречается иначе, чем в результате вызова функции
abort
илиraise
, поведение не определено, если обработчик сигнала ссылается на любой объект со статикой или продолжительностью хранения streamов, который не является блокирующим атомом, кроме присвоения значение для объекта, объявленного какvolatile sig_atomic_t
, или обработчик сигнала вызывает любую функцию в стандартной библиотеке,_Exit
функции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()
и др. – нельзя использовать безопасно).
Большое приложение, с которым я работал в начале 1990-х годов, содержало обработку исключений на основе C с использованием setjmp и longjmp. Ключевое слово volatile было необходимо для переменных, значения которых необходимо сохранить в блоке кода, который служил в качестве предложения catch, чтобы эти вары не хранились в регистрах и не были уничтожены longjmp.
При разработке для встроенного я имею цикл, который проверяет переменную, которая может быть изменена в обработчике прерываний. Без «volatile» цикл становится noop – насколько компилятор может сказать, переменная никогда не изменяется, поэтому она оптимизирует проверку.
То же самое относится к переменной, которая может быть изменена в другом streamе в более традиционной среде, но там мы часто выполняем вызовы синхронизации, поэтому компилятор не настолько свободен от оптимизации.
Я использовал его в assemblyх отладки, когда компилятор настаивает на оптимизации переменной, которую я хочу видеть, когда я перехожу через код.
- вы должны использовать его для реализации шпиндельных замков, а также для некоторых (всех?) блокировок данных
- использовать его с атомными операциями / инструкциями
- помог мне однажды преодолеть ошибку компилятора (ошибочно сгенерированный код во время оптимизации)
Помимо использования его по назначению, летучие используются в метапрограммировании (шаблоне). Его можно использовать для предотвращения случайной перегрузки, так как вовнутримый атрибут (например, const) принимает участие в разрешении перегрузки.
template class Foo { std::enable_if_t f(T& t) { std::cout << 1 << t; } void f(T volatile& t) { std::cout << 2 << const_cast(t); } void bar() { T t; f(t); } };
Это законно; обе перегрузки потенциально вызываемы и делают почти то же самое. Приведение в volatile
перегрузке является законным, поскольку мы знаем, что бар в любом случае не будет передавать энергонезависимый T
Однако volatile
версия строго хуже, поэтому никогда не выбирается при разрешении перегрузки, если имеется энергонезависимая f
.
Обратите внимание, что код никогда не зависит от доступа к volatile
памяти.
Ключевое слово volatile
предназначено для предотвращения использования компилятором каких-либо оптимизаций для объектов, которые могут быть изменены способами, которые не могут быть определены компилятором.
Объекты, объявленные как volatile
, исключаются из оптимизации, поскольку их значения могут быть изменены кодом за пределами текущего кода в любое время. Система всегда считывает текущее значение volatile
объекта из ячейки памяти, а не сохраняет его значение во временном регистре в той точке, в которой оно запрашивается, даже если предыдущая команда запрашивала значение от одного и того же объекта.
Рассмотрим следующие случаи
1) Глобальные переменные, измененные процедурой обслуживания прерываний вне области видимости.
2) Глобальные переменные в многопоточном приложении.
Если мы не используем изменчивый classификатор, могут возникнуть следующие проблемы
1) Если оптимизация включена, код может работать не так, как ожидалось.
2) Код может работать не так, как ожидалось, когда прерывания разрешены и используются.
Неустойчивый: лучший друг программиста
https://en.wikipedia.org/wiki/Volatile_(computer_programming)
Помимо того, что ключевое слово volatile используется для указания компилятору не оптимизировать доступ к какой-либо переменной (которая может быть изменена streamом или подпрограммой прерывания), она также может использоваться для удаления некоторых ошибок компилятора – ДА, это может быть —.
Например, я работал над встроенной платформой, когда компилятор делал некоторые неправильные настройки относительно значения переменной. Если код не был оптимизирован, программа будет работать нормально. С оптимизацией (которая была действительно необходима, потому что это была критическая процедура), код не работал бы корректно. Единственное решение (хотя и не очень корректное) заключалось в том, чтобы объявить «неисправную» переменную нестабильной.
Одно из них, о котором я должен напомнить, – в функции обработчика сигнала, если вы хотите получить доступ / изменить глобальную переменную (например, пометить ее как exit = true), вы должны объявить эту переменную как «volatile».
Ваша программа работает даже без ключевого слова volatile
? Возможно, это и есть причина:
Как упоминалось ранее, ключевое слово volatile
помогает в таких случаях, как
volatile int* p = ...; // point to some memory while( *p!=0 ) {} // loop until the memory becomes zero
Но кажется, что эффект почти не срабатывает после вызова внешней или не встроенной функции. Например:
while( *p!=0 ) { g(); }
Затем с изменением или без него генерируется почти тот же результат.
Пока g () может быть полностью встроен, компилятор может видеть все, что происходит, и поэтому может оптимизировать. Но когда программа делает вызов в место, где компилятор не может видеть, что происходит, для компилятора небезопасно делать какие-либо предположения. Следовательно, компилятор будет генерировать код, который всегда читает из памяти напрямую.
Но остерегайтесь того дня, когда ваша функция g () становится встроенной (из-за явных изменений или из-за умения компилятора / компоновщика), тогда ваш код может сломаться, если вы забыли ключевое слово volatile
!
Поэтому я рекомендую добавить ключевое слово volatile
даже если ваша программа работает без него. Это делает цель более четкой и надежной в отношении будущих изменений.
В первые дни C компиляторы интерпретировали все действия, которые читают и записывают lvalues в качестве операций с памятью, которые должны выполняться в той же последовательности, что и чтения и записи, появившиеся в коде. Во многих случаях эффективность могла бы значительно улучшиться, если компиляторам была предоставлена определенная свобода для переоформления и консолидации операций, но с этим возникла проблема. Даже операции часто указывались в определенном порядке только потому, что необходимо было указать их в некотором порядке, и, таким образом, программист выбрал одну из многих одинаково хороших альтернатив, что не всегда было так. Иногда было бы важно, чтобы определенные операции выполнялись в определенной последовательности.
Именно то, какие детали последовательности важны, будет зависеть от целевой платформы и области приложения. Вместо того, чтобы предоставлять особенно подробный контроль, стандарт выбрал простую модель: если последовательность обращений выполняется с lvalues, которые не являются квалифицированными volatile
, компилятор может изменить порядок и консолидировать их по своему усмотрению. Если действие выполняется с volatile
-qualified lvalue, реализация качества должна предлагать любые дополнительные гарантии заказа, которые могут потребоваться при кодовом таргетинге на предполагаемую область платформы и приложения, без необходимости использования нестандартного синтаксиса.
К сожалению, вместо того, чтобы определить, какие гарантии потребуют программисты, многие компиляторы предпочли вместо этого предложить минимальные гарантии, гарантированные Стандартом. Это делает volatile
гораздо менее полезным, чем должно быть. Например, в gcc или clang программист, которому необходимо реализовать базовый «мьютекс-отключение» [тот, где задача, которая приобрела и выпустила мьютекс, не будет делать этого снова, пока другая задача не сделает это], должна сделать одно из четырех вещей:
-
Поместите обнаружение и освобождение мьютекса в функции, которую компилятор не может встроить, и к которой он не может применить всю оптимизацию программы.
-
Квалифицируйте все объекты, защищенные мьютексом, как
volatile
то, что не обязательно, если все обращения возникают после получения мьютекса и до его выпуска. -
Используйте уровень оптимизации 0, чтобы заставить компилятор генерировать код, как если бы все объекты, которые не являются квалифицированным
register
,volatile
. -
Используйте директивы gcc-specific.
В отличие от этого, при использовании более качественного компилятора, который более подходит для системного программирования, например icc, у вас будет еще один вариант:
- Убедитесь, что
volatile
квалифицированная запись выполняется везде, где требуется приобретение или выпуск.
Приобретение базового «мьютекса передачи вручную» требует volatile
чтения (чтобы убедиться, что оно готово), и также не должно требовать volatile
записи (другая сторона не будет пытаться повторно приобрести ее до тех пор, пока она не будет возвращена), но необходимость выполнять бессмысленную volatile
запись по-прежнему лучше любого из вариантов, доступных под gcc или clang.