Почему летучие существуют? - PullRequest
193 голосов
/ 16 сентября 2008

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

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

Ответы [ 16 ]

239 голосов
/ 16 сентября 2008

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

77 голосов
/ 16 сентября 2008

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

68 голосов
/ 16 сентября 2008

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

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

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

43 голосов
/ 16 сентября 2008

Из статьи о встраиваемых системах Дэна Сакса:

"Под изменчивым объектом понимается объект, значение которого может изменяться самопроизвольно. То есть, когда вы объявляете объект изменчивым, вы сообщаете компилятору, что объект может изменить состояние, даже если в программе нет операторов, способных изменить его. . "

Ссылки на 2 замечательные статьи г-на Сакса относительно изменчивого ключевого слова:

http://www.embedded.com/columns/programmingpointers/174300478 http://www.embedded.com/columns/programmingpointers/175801310

23 голосов
/ 17 сентября 2008

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

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

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

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);
10 голосов
/ 27 июля 2013

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

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

¶5 Если сигнал возникает не в результате вызова функции 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 голосов
/ 17 сентября 2008

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

7 голосов
/ 17 сентября 2008

Помимо использования по назначению, 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 голосов
/ 17 сентября 2008

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

7 голосов
/ 16 сентября 2008

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

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

...