Почему существует volatile?



что значит volatile ключевое слово do? В C++ какую проблему он решает?



в моем случае, я никогда не нуждался в нем.

669   16  

16 ответов:

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 большие статьи г-на Сакса относительно летучих ключевое слово:

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
);

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

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

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

¶5 Если сигнал возникает не в результате вызова abort или

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Я должен напомнить вам, что в функции обработчика сигналов, Если вы хотите получить доступ/изменить глобальную переменную (например, пометить ее как 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(); }

тогда с или без volatile генерируется почти тот же результат.

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

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

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

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

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

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

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

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

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

  4. используйте GCC-specific директивы.

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

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

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

Comments

    Ничего не найдено.