В C++ есть ключевые слова, окруженные некой аурой загадочности. Просто потому, что используются они редко, в специфических случаях. volatile, mutable, typename, explicit... Несмотря на редкость использования все равно интересно знать что это такое. Да и вообще как-то неприятно, когда встречаешь и не знаешь что это. volatile мне встречалось дважды. И оба раза мне не сразу удавалось понять, что же хотел сказать автор. При первом взгляде, это ключевое слово, скорее всего, вызовет какие-то смутные ассоциации: "мммм..... это вроде что-то связанное с потоками" (здесь речь о потоках, которые "thread", а не которые "stream"). На самом деле, указывая volatile при объявлении переменной, программист просит компилятор не оптимизировать эту переменную.
Компилятор скорее всего оптимизирует код вроде такого, если переменная cancel не менялась в теле цикла.
bool cancel = false;
while( !cancel ) {
;
}Если
cancel не меняется, то ее и проверять каждый раз незачем, компилятор и не будет ее проверять.Зато если вы укажете перед переменной
volatile, то оптимизиации не будет. Предполагается, что переменная cancel могла измениться каким-то волшебным образом.
volatile bool cancel = false;
while( !cancel ) {
;
}
Каким волшебным образом? Из другого потока? Но обеспечивать доступ к переменным из разных потоков с помощью
volatile не есть хорошо. Потому что переменную надо лочить при считывании и записи. Чтобы не получилось, например, такой ситуации: один поток пишет в переменную, другой в этот же момент считывает и считает наполовину старое значение, наполовину новое, короче не пойми что получится. А если ее лочить, то и volatile не нужен. Доступ к переменным может лочиться по-разному, но по тому, что я читала в форумах, где бы вы ни лочили, volatile в дополнение к локу указывать не нужно. Более того, использование volatile в данной ситуации может сказаться на производительности.(Updated 09.01.2009: это не так как минимум для gcc 3.3.6)Чисто теоретически указания
volatile при работе с потоками достаточно, если тип данных, с которым идет работа, может быть записан на данной архитектуре атомарно, в один прием. Соответственно, надо точно знать к каким именно типам это относится. Казалось бы уж что-что, так bool должен писаться в один прием. Я вычитала, что на некоторых Windows'ах это вовсе даже и не так. И атомарность присутствует только при работе с char...Несмотря ни на что,
volatile таки используется для доступа к переменной из разных потоков. Глобальная переменная объявляется volatile и вперед. И дважды я встретилась именно с таким использованием volatile. Но, как уже сказано выше, это не корректно.Update 3.11.2006
volatile в исполнении Микрософт имеет Microsoft Specific пункт. А именно: атомарность операций гарантируется и, как следствие, использование в многопоточных программах приветствуется. Но код получается непортируемым, соответственно.
Я нашла этот пункт только в документации к Visual Studio 2005. Я порылась на MSDN в поисках этого пункта в других версиях Visual Studio, не нашла.
А зачем вообще тогда нужно ключевое слово
volatile? Джеймс Канзе пишет, что задумывался volatile для работы с memory mapped IO (MMIO). Интересно он пишет, переведу кусок подробно
На уровне железа многие процессоры просто резервируют блок адресов памяти для портов ввода-вывода. Большинство процессоров имеют отдельное пространство адресов ввода-вывода, со специальными инструкциями для доступа туда, но это не универсально (на PDP-11 такого не было, например) и даже сейчас, производители железа могут предпочесть использовать для этого адресное пространство памяти, по разным причинам. Я сомневаюсь, что кто-то так делает на архитектуре 8086 - различные адресные ограничения делают это очень сложным. Я видел это на 8080, это очень частое решение на старой TI 9900. И это был единственный способ организовать ввод-вывод на наборе инструкций PDP-11, там просто не было отдельного адресного пространства ввода-вывода (Я думаю, то же самое верно и для VAX. И не забывайте, что большинство работы на С раньше проходило именно на этих двух процессорах).
Теперь рассмотрим один из первых последовательных портов, что я использовал: Intel 8051. Нормальным способом его инициализации было записать 0 три раза в порт управления. Если у вас MMIO, то код на С мог бы выглядеть примерно так:
unsigned char* pControl = 0xff24 ;
*pControl = 0 ;
*pControl = 0 ;
*pControl = 0 ;
Что прекрасно работало на всех компиляторах С. Но вы можете себе представить, что могла бы с этим сделать сама простая оптимизация. По этой причине и нужно было ключевое слово volatile, чтобы сказать компилятору не оптимизировать.
Еще пример из того же обсуждения.
char getChar()
{
static unsigned char const charAvail = 0x01 ;
volatile unsigned char* pStatus = (unsigned char*)0x1234 ;
volatile char* pData = (char*)0x1230 ;
while ( (*pStatus & charAvail) == 0 ) {
}
return *pData ;
}
Без
volatile переменная pStatus была бы скорее всего считана лишь один раз на регистр, и работа бы проходила с ее копией на регистре. С volatile считывание будет проходить в цикле каждый раз.Это все не очень актуально для Windows, Unix систем, там есть свои способы работы с MMIO.
Еще
volatile, вернее выражение вида volatile sig_atomic_t, может понадобиться для работы с сигналами. По сигналам я откопала большой FAQ на русском языке, про volatile sig_atomic_t там есть.volatile используется с setjmp/longjmp, для восстановления значения переменных. Это тоже весьма редко используемые функции, и, судя по тому, что я о них читала, компиляторы почти всегда при их использовании все сами могут восстанавливать. Если вы их используете или встретили старый код с ними, то вот, зачем тут может быть volatile: Volatile type or qualifier?.Кроме
volatile переменных бывают еще и volatile функции и volatile классы. Я знаю лишь один случай их применения. Есть весьма известная и очень критикуемая статья Андрея Александреску volatile - Multithreaded Programmer's Best Friend, где он, используя volatile, смог добиться безопасной работы с потоками с проверкой на этапе компиляции.Он использует только синтаксические свойства
volatile. Связь volatile и оптимизации никак не используется. С тем же успехом он мог бы использовать и const. Но const гораздо чаще применяется по своему прямому назначению, чем volatile, поэтому использование const создало бы больше неудобств.В начале следующей статьи Андрей суммирует критику Generic: Min and Max Redivivus. По поводу чего критика... Во-первых, он начинает статью с того, что рассказывает о том, как хорошо
volatile применять при многопоточном программировании. Что, как было сказано выше, вообще говоря неверно. Потом он снимает volatile с помощью const_cast с переменной, которая реально является volatile. Несмотря на то, что скорее всего ничем плохим это не обернется, по Стандарту C++ эта ситуация приводит к undefined behavior. Но все это ничуть не умаляет значимость этой гениальной задумки. Насколько ее удобно использовать в реальном коде это вопрос, но хотя бы с теоретической точки зрения она представляет большой интерес.Вот и все, что мне удалось найти о
volatile. Пост получился не о том как лучше и удобнее применять volatile, а скорее о том, что думать, когда вы встретите volatile в чужом коде. А вообще можно всю жизнь программировать на C++, но с volatile так и не встретиться.Ссылки по теме:
Подробные и наглядные примеры работы компилятора при указании volatile с диассемблированными примерами кода: раз и два.
comp.lang.c++.moderated: Q: infos, articles, faqs about 'volatile'
comp.lang.c++.moderated: volatile, was: memory visibility between threads - очень большое обсуждение статей Александреску.
comp.lang.c++.moderated: volatile -- what does it mean in relation to member functions?
Doug Harrison Microsoft MVP - Visual C++ об использовании volatile
comp.lang.c++.moderated: Why use volatile anyway?
comp.programming.threads FAQ



Atom feed