Квалификатор volatile
Квалификатор volatile
говорит компилятору, что оптимизировать данную переменную запрещено, и что компилятор никогда не сможет предвидеть или вычислить ее значение заранее.
volatile int flag = 42;
Когда это вообще нужно? Рассмотрим следующий пример:
bool cancel = false;
/* ... */
while (!cancel) {
/* ... */
}
Если в теле цикла переменная cancel
не меняется, то компилятор может соптимизировать ее проверку, и в цикл мы не войдем никогда.
Таким и должно быть наблюдаемое поведение, если мы не пишем сложной логики, например, если переменная cancel
не шарится с другими потоками или процессами, которые могут ее перезаписывать. Использование volatile
нужно для предупреждения компилятора, что магические силы могут изменить значение переменной в любой момент и нельзя опираться на то, что в нее было записано в compile-time.
Другой пример:
unsigned char* pControl = 0xff24;
void f() {
*pControl = 1;
*pControl = 0;
*pControl = 0;
}
Компилятор может опустить первые две операции присваивания, оставив только последнюю. Когда это может быть вредно? При разработке программ, работающих с Memory mapped IO
, то есть взаимодействующих с какими-то железками через оперативную память, или при разработке драйверов, где важно каждое переданное значение.
Использование volatile
запрещает перегруппировку инструкций доступа и их оптимизацию, но ни в коем случае не гарантирует атомарности. Чтение volatile
переменной при одновременной записи в нее из другого потока или одновременная запись из разных потоков без синхронизации - это data race.
Также использование volatile
запрещает использование переменной из регистра - каждое чтение будет связано с загрузкой из оперативной памяти. Это свойство кажется уже очевидным, но тем не менее стоит подчеркнуть, что такое поведение можно использовать для каких-нибудь бенчмарков.
Разумеется, нельзя использовать волатильную переменную со снятым позднее через const_cast
квалификатором volatile
- это undefined behavior.
Связь с квалификатором const
Как и const
, volatile
- это cv-квалификатор, разумеется, поэтому нужно строго понимать, куда писать это слово в объявлении типа.
Пример выше исправляется следующим образом, потому что в функции f
мы делаем присвоения в unsigned char&:
volatile unsigned char* pControl = 0xff24;
Но если нужна именно volatile
-переменная, а не данные по указателю, то это должно писаться следующим образом:
unsigned char* volatile pControl = 0xff24;
Заимствования: