Существует интригующая техника, которая заставляет ваш компилятор обнаруживать возможные условия гонки, в значительной степени зависящие от ключевого слова volatile, вы можете прочитать об этом по адресу http://www.ddj.com/cpp/184403766 .
Нено Ганчев,
Это хороший ресурс с примером того, когда его volatileможно эффективно использовать, собрав в довольно обыденных терминах. Ссылка: publishing.gbdirect.co.uk/c_book/chapter8/…
Оптимизированный кодер
Я использую его для блокировки без кода / двойной проверки блокировки
Пол
Для меня volatileполезнее, чем friendключевое слово.
acegs
Ответы:
268
volatile нужен, если вы читаете из области памяти, в которой, скажем, совершенно отдельный процесс / устройство / все, что может записывать.
Раньше я работал с двухпортовым ОЗУ в многопроцессорной системе на прямом C. Мы использовали 16-битное значение с аппаратным управлением как семафор, чтобы знать, когда другой парень закончил. По сути, мы сделали это:
void waitForSemaphore(){volatileuint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/while((*semPtr)!= IS_OK_FOR_ME_TO_PROCEED);}
Без volatileэтого оптимизатор считает цикл бесполезным (парень никогда не устанавливает значение! Он сошел с ума, избавьтесь от этого кода!), И мой код продолжит работу, не получив семафор, что впоследствии вызовет проблемы.
В этом случае, что случилось бы, если бы uint16_t* volatile semPtrбыло написано вместо? Это должно пометить указатель как volatile (вместо значения, на которое указывает), так что проверки самого указателя, например, semPtr == SOME_ADDRмогут быть не оптимизированы. Это, однако, также подразумевает волатильное указанное значение. Нет?
Зил
@ Зил Нет, это не так. На практике то, что вы предлагаете, скорее всего, произойдет. Но теоретически можно получить компилятор, который оптимизирует доступ к значениям, потому что он решил, что ни одно из этих значений никогда не изменяется. И если бы вы имели в виду volatile для применения к значению, а не к указателю, вы бы облажались. Опять же, маловероятно, но лучше ошибиться, если поступить правильно, чем воспользоваться поведением, которое случается сегодня.
@curiousguy это не решило неправильно. Он сделал правильный вывод на основе предоставленной информации. Если вам не удастся пометить что-то изменчивое, компилятор может предположить, что оно не является изменчивым . Это то, что делает компилятор при оптимизации кода. Если есть больше информации, а именно, что эти данные на самом деле изменчивы, то ответственность за предоставление этой информации лежит на программисте. То, на что вы претендуете с ошибочным компилятором, на самом деле просто плохое программирование.
iheanyi
1
@curiousguy нет, только то, что ключевое слово volatile появляется один раз, не означает, что все внезапно становится изменчивым. Я привел сценарий, в котором компилятор делает правильные вещи и достигает результата, противоречащего ошибочному ожиданиям программиста. Точно так же, как «самый неприятный синтаксический анализ» не является признаком ошибки компилятора, как и здесь.
iheanyi
82
volatileнеобходим при разработке встроенных систем или драйверов устройств, где необходимо читать или записывать отображаемое в память аппаратное устройство. Содержимое конкретного регистра устройства может измениться в любое время, поэтому вам нужно volatileключевое слово, чтобы компилятор не оптимизировал такой доступ.
Это относится не только к встроенным системам, но и ко всем разработкам драйверов устройств.
Младен Янкович
Единственный раз, когда я нуждался в этом на 8-битной шине ISA, где вы дважды читали один и тот же адрес - у компилятора была ошибка, и он ее игнорировал (ранний Zortech c ++)
Мартин Беккет
Volatile очень редко подходит для управления внешними устройствами. Его семантика неправильна для современного MMIO: вы должны сделать слишком много объектов нестабильными, и это вредит оптимизации. Но современный MMIO ведет себя как обычная память, пока не установлен флаг, так что энергозависимость не требуется. Многие водители никогда не используют энергозависимые.
любопытный парень
69
Некоторые процессоры имеют регистры с плавающей запятой, которые имеют точность более 64 бит (например, 32-битный x86 без SSE, см. Комментарий Питера). Таким образом, если вы выполняете несколько операций над числами с двойной точностью, вы на самом деле получаете ответ с более высокой точностью, чем если бы вы усекали каждый промежуточный результат до 64 бит.
Обычно это замечательно, но это означает, что в зависимости от того, как компилятор назначил регистры и выполнил оптимизацию, вы получите разные результаты для точно таких же операций с одинаковыми входными данными. Если вам нужна согласованность, вы можете заставить каждую операцию вернуться в память, используя ключевое слово volatile.
Это также полезно для некоторых алгоритмов, которые не имеют алгебраического смысла, но уменьшают ошибку с плавающей запятой, например, суммирование по Кахану. Алгебраически это не nop, поэтому он часто будет неправильно оптимизирован, если некоторые промежуточные переменные не являются изменчивыми.
Когда вы вычисляете числовые производные, это также полезно, чтобы убедиться, что x + h - x == h, вы определяете hh = x + h - x как изменчивый, чтобы можно было вычислить правильную дельту.
Александр С.
5
+1, действительно, по моему опыту, был случай, когда вычисления с плавающей запятой давали разные результаты в Debug и Release, поэтому модульные тесты, написанные для одной конфигурации, не выполнялись для другой. Мы решили это путем объявления одной переменной с плавающей точкой volatile doubleвместо простой double, чтобы обеспечить ее усечение с точности FPU до 64-битной (RAM) точности перед продолжением дальнейших вычислений. Результаты существенно отличались из-за дальнейшего преувеличения ошибки с плавающей точкой.
Серж Рогач,
Ваше определение «современный» немного не так. Это касается только 32-битного кода x86, который избегает SSE / SSE2, и он не был «современным» даже 10 лет назад. Все MIPS / ARM / POWER имеют 64-битные аппаратные регистры, как и x86 с SSE2. Реализации C ++ x86-64 всегда используют SSE2, и у компиляторов есть опции, такие как g++ -mfpmath=sseиспользовать его и для 32-битной x86. Вы можете использовать gcc -ffloat-storeдля принудительного округления везде, даже при использовании x87, или вы можете установить точность x87 на 53-битную мантиссу: randomascii.wordpress.com/2012/03/21/… .
Питер Кордес
Но все же хороший ответ: для устаревшего кода x87 вы можете использовать его volatileдля принудительного округления в нескольких конкретных местах, не теряя преимущества везде.
Питер Кордес
1
Или я ошибочно путаю с непоследовательным?
Чипстер
49
Из статьи Дэна Сакса «Волатильно как обещание» :
(...) изменчивый объект - это объект, значение которого может меняться самопроизвольно. То есть, когда вы объявляете объект энергозависимым, вы сообщаете компилятору, что объект может изменить состояние, даже если никакие операторы в программе не изменяют его ».
Вот ссылки на три его статьи относительно volatileключевого слова:
Вы ДОЛЖНЫ использовать volatile при реализации структур данных без блокировки. В противном случае компилятор может оптимизировать доступ к переменной, что изменит семантику.
Иными словами, volatile сообщает компилятору, что доступ к этой переменной должен соответствовать операции чтения / записи в физической памяти.
Например, вот как InterlockedIncrement объявляется в Win32 API:
LONG __cdecl InterlockedIncrement(
__inout LONG volatile*Addend);
Вам абсолютно НЕ нужно объявлять переменную volatile, чтобы иметь возможность использовать InterlockedIncrement.
любопытный парень
Теперь этот ответ устарел, поскольку в C ++ 11 std::atomic<LONG>вы можете писать код без блокировок более безопасно, без проблем с оптимизацией чистых загрузок / чистых хранилищ, переупорядочением или чем-то еще.
Питер Кордес
10
Большое приложение, над которым я работал в начале 1990-х годов, содержало обработку исключений на основе C с использованием setjmp и longjmp. Ключевое слово volatile было необходимо для переменных, значения которых нужно было сохранить в блоке кода, который служил в качестве предложения «catch», чтобы эти переменные не сохранялись в регистрах и не стирались longjmp.
В стандарте C одно из мест для использования volatile- с обработчиком сигнала. Фактически, в стандарте C все, что вы можете безопасно сделать в обработчике сигналов, это изменить volatile sig_atomic_tпеременную или быстро выйти. Действительно, AFAIK, это единственное место в Стандарте C, для volatileкоторого требуется использование, чтобы избежать неопределенного поведения.
ISO / IEC 9899: 2011 §7.14.1.1 signalФункция
If5 Если сигнал возникает не в результате вызова функции abortили raise, поведение не определено, если обработчик сигнала ссылается на какой-либо объект со статическим или потоковым сроком хранения, который не является атомарным объектом без блокировки, кроме как путем присвоения значения объекту, объявленному как volatile sig_atomic_t, или обработчик сигнала вызывает любую функцию в стандартной библиотеке, кроме abortфункции, _Exitфункции,
quick_exitфункции или signalфункции с первым аргументом, равным номеру сигнала, соответствующему сигналу, вызвавшему вызов обработчик. Кроме того, если такой вызов signalфункции приводит к возвращению SIG_ERR, значение errnoявляется неопределенным. 252)
252) Если какой-либо сигнал генерируется асинхронным обработчиком сигнала, поведение не определено.
Это означает, что в стандарте C вы можете написать:
POSIX гораздо более снисходительно относится к тому, что вы можете делать в обработчике сигналов, но есть все еще ограничения (и одно из ограничений заключается в том, что стандартную библиотеку ввода-вывода printf()и т. Д. Нельзя безопасно использовать).
Разрабатывая для встроенного, у меня есть цикл, который проверяет переменную, которая может быть изменена в обработчике прерываний. Без «volatile» цикл превращается в петлю - насколько может сказать компилятор, переменная никогда не меняется, поэтому она оптимизирует проверку.
То же самое относится и к переменной, которая может быть изменена в другом потоке в более традиционной среде, но там мы часто делаем вызовы синхронизации, поэтому компилятор не так свободен с оптимизацией.
Помимо использования по назначению, volatile используется в (шаблонном) метапрограммировании. Его можно использовать для предотвращения случайной перегрузки, поскольку атрибут volatile (например, const) участвует в разрешении перегрузки.
Это законно; Обе перегрузки потенциально могут быть вызваны и почти одинаковы. Состав в volatileперегрузке является законным, поскольку мы знаем, что бар все Tравно не пройдет энергонезависимый . volatileВерсия не строго хуже, хотя, так и не выбрали в разрешении перегрузки , если энергонезависимая fдоступен.
Обратите внимание, что код никогда не зависит от volatileдоступа к памяти.
Не могли бы вы пояснить это на примере? Это действительно поможет мне лучше понять. Спасибо!
батбрат
« Приведение в энергозависимой перегрузке » Приведение является явным преобразованием. Это конструкция SYNTAX. Многие люди путают это (даже стандартные авторы).
любопытный парень
6
Вы должны использовать его для реализации спин-блокировок, а также некоторых (всех?) структур данных без блокировки
используйте его с атомарными операциями / инструкциями
помог мне однажды преодолеть ошибку компилятора (неправильно сгенерированный код во время оптимизации)
Вам лучше использовать библиотеку, встроенные функции компилятора или встроенный ассемблерный код. Летучий ненадежен.
Zan Lynx
1
1 и 2 оба используют атомарные операции, но volatile не обеспечивает атомарную семантику, и специфичные для платформы реализации atomic заменят необходимость использования volatile, поэтому для 1 и 2, я не согласен, вам не нужно volatile для них.
Кто что-нибудь говорит о нестабильном обеспечении атомарной семантики? Я сказал, что вам нужно ИСПОЛЬЗОВАТЬ volatile с атомарными операциями, и если вы не думаете, что это правда, посмотрите на объявления о взаимосвязанных операциях API win32 (этот парень также объяснил это в своем ответе)
Младен Янкович,
4
volatileКлючевое слово предназначено для предотвращения компилятора применения каких - либо оптимизаций на объектах , которые могут изменить таким образом , что не может быть определенно с помощью компилятора.
Объекты, объявленные как volatile, исключаются из оптимизации, потому что их значения могут быть изменены кодом вне области текущего кода в любое время. Система всегда считывает текущее значение volatileобъекта из области памяти, а не сохраняет его значение во временном регистре в той точке, в которой оно запрашивается, даже если предыдущая инструкция запрашивала значение из того же объекта.
Рассмотрим следующие случаи
1) Глобальные переменные, измененные подпрограммой обработки прерываний вне области действия.
2) Глобальные переменные в многопоточном приложении.
Если мы не используем volatile квалификатор, могут возникнуть следующие проблемы
1) Код может не работать должным образом при включенной оптимизации.
2) Код может не работать должным образом, когда прерывания включены и используются.
Ссылка, которую вы разместили, крайне устарела и не отражает современные лучшие практики.
Тим Сегин
2
Помимо того, что ключевое слово volatile используется для указания компилятору не оптимизировать доступ к некоторой переменной (которая может быть изменена потоком или подпрограммой прерывания), оно также может использоваться для удаления некоторых ошибок компилятора - ДА, это может быть ---
Например, я работал над встроенной платформой, где компилятор делал неправильные предположения относительно значения переменной. Если код не был оптимизирован, программа будет работать нормально. С оптимизацией (которая была действительно необходима, потому что это была критическая процедура) код не работал бы правильно. Единственное решение (хотя и не очень правильное) состояло в том, чтобы объявить переменную «неисправный» как volatile.
Это ошибочное предположение, что компилятор не оптимизирует доступ к летучим компонентам. Стандарт ничего не знает об оптимизации. Компилятор обязан уважать то, что предписывает стандарт, но он может делать любые оптимизации, которые не мешают нормальному поведению.
Конец
3
Исходя из моего опыта, 99,9% всех «ошибок» оптимизации в gcc arm являются ошибками со стороны программиста. Не знаю, относится ли это к этому ответу. Просто разглагольствовать на общую тему
odinthenerd
@ Terminus " Это ошибочное предположение, что компилятор не оптимизирует доступ к летучим компонентам " Источник?
любопытный парень
2
Кажется, ваша программа работает даже без volatileключевого слова? Возможно, это причина
Как упоминалось ранее, volatileключевое слово помогает в таких случаях, как
volatileint* p =...;// point to some memorywhile(*p!=0){}// loop until the memory becomes zero
Но, похоже, эффект почти не проявляется после вызова внешней или не встроенной функции. Например:
while(*p!=0){ g();}
Затем с или без volatileпочти такой же результат генерируется.
Пока g () может быть полностью встроенным, компилятор может видеть все, что происходит, и поэтому может оптимизировать. Но когда программа обращается к месту, где компилятор не может видеть, что происходит, компилятору небезопасно делать какие-либо предположения. Следовательно, компилятор будет генерировать код, который всегда читает непосредственно из памяти.
Но остерегайтесь того дня, когда ваша функция g () станет встроенной (либо из-за явных изменений, либо из-за хитрости компилятора / компоновщика), тогда ваш код может сломаться, если вы забудете volatileключевое слово!
Поэтому я рекомендую добавить volatileключевое слово, даже если ваша программа работает без него. Это делает намерение более ясным и надежным в отношении будущих изменений.
Обратите внимание, что у функции может быть встроенный код, в то же время генерируя ссылку (разрешенную во время ссылки) на функцию структуры; это будет случай частично встроенной рекурсивной функции. Функция также может иметь свою семантическую «встроенную» компилятором, то есть компилятор предполагает, что побочные эффекты и результат находятся в пределах возможных побочных эффектов и результатов, возможных в соответствии с его исходным кодом, но все еще не включаются в него. Это основано на «действующем правиле единого определения», которое гласит, что все определения сущности должны быть фактически эквивалентны (если не точно идентичны).
любопытный парень
Избегать переносимого встраивания вызова (или «встраивания» его семантики) с помощью функции, тело которой видно компилятору (даже во время компоновки с глобальной оптимизацией), возможно с помощью volatileквалифицированного указателя на функцию:void (* volatile fun_ptr)() = fun; fun_ptr();
curiousguy
2
В первые дни C компиляторы интерпретировали все действия, которые читают и записывают значения l, как операции с памятью, которые должны выполняться в той же последовательности, в которой операции чтения и записи появились в коде. Во многих случаях эффективность могла бы быть значительно улучшена, если бы компиляторам была предоставлена определенная свобода для переупорядочения и консолидации операций, но с этим была проблема. Даже операции часто указывались в определенном порядке просто потому, что необходимо было указывать их в каком-то порядке, и, таким образом, программист выбрал одну из многих одинаково хороших альтернатив, что не всегда имело место. Иногда было бы важно, чтобы определенные операции происходили в определенной последовательности.
Какие именно детали последовательности важны, зависит от целевой платформы и области применения. Вместо того, чтобы предоставлять особенно подробный контроль, Стандарт выбрал простую модель: если последовательность обращений выполняется с l-значениями, которые не квалифицированы volatile, компилятор может переупорядочить и объединить их по своему усмотрению. Если действие выполняется с volatile-качественным lvalue, качественная реализация должна предлагать любые дополнительные гарантии упорядочения, которые могут потребоваться при коде, предназначенном для его предполагаемой платформы и области приложения, без необходимости использования нестандартного синтаксиса.
К сожалению, вместо того, чтобы определить, какие гарантии понадобятся программистам, многие компиляторы предпочли предложить минимальные гарантии, предусмотренные стандартом. Это делает volatileгораздо менее полезным, чем должно быть. Например, в gcc или clang программист, которому необходимо реализовать базовый «мьютекс передачи» [тот, в котором задача, которая получила и освободил мьютекс, не будет делать это до тех пор, пока другая задача не выполнит это], должен выполнить один из четырех вещей:
Поместите получение и освобождение мьютекса в функцию, которую компилятор не может встроить и к которой он не может применить Оптимизацию всей программы.
Определите все объекты, охраняемые мьютексом, как volatile-что-то, что не должно быть необходимым, если все обращения происходят после получения мьютекса и перед его освобождением.
Используйте уровень оптимизации 0 , чтобы заставить компилятор генерировать код , как если бы все объекты, которые не квалифицированы registerявляются volatile.
Используйте специфичные для gcc директивы.
Напротив, при использовании более качественного компилятора, который больше подходит для системного программирования, такого как icc, можно было бы выбрать другой вариант:
Убедитесь, что volatile-качественная запись выполняется везде, где требуется приобретение или выпуск.
Для получения базового «мьютекса ручной передачи» требуется volatileчтение (чтобы увидеть, готово ли оно), и не требуется также и volatileзапись (другая сторона не будет пытаться повторно получить ее, пока она не будет возвращена), но для этого нужно выполнять бессмысленную volatileзапись все же лучше, чем любой из параметров, доступных в gcc или clang.
Я должен напомнить вам, что в функции-обработчике сигналов вы можете использовать глобальную переменную (например, пометить ее как exit = true) и объявить ее как 'volatile'.
Все ответы отличные. Но вдобавок ко всему, я хотел бы поделиться примером.
Ниже приведена небольшая программа cpp:
#include<iostream>int x;int main(){char buf[50];
x =8;if(x ==8)
printf("x is 8\n");else
sprintf(buf,"x is not 8\n");
x=1000;while(x >5)
x--;return0;}
Теперь давайте сгенерируем сборку из приведенного выше кода (и я вставлю только те части сборки, которые здесь уместны):
Команда для генерации сборки:
g++-S -O3 -c -fverbose-asm-Wa,-adhln assembly.cpp
И сборка:
main:.LFB1594:
subq $40,%rsp #,.seh_stackalloc 40.seh_endprologue
# assembly.cpp:5: int main(){
call __main ## assembly.cpp:10: printf("x is 8\n");
leaq .LC0(%rip),%rcx #,# assembly.cpp:7: x = 8;
movl $8, x(%rip)#, x# assembly.cpp:10: printf("x is 8\n");
call _ZL6printfPKcz.constprop.0## assembly.cpp:18: }
xorl %eax,%eax #
movl $5, x(%rip)#, x
addq $40,%rsp #,
ret
.seh_endproc
.p2align 4,,15.def _GLOBAL__sub_I_x;.scl 3;.type 32;.endef
.seh_proc _GLOBAL__sub_I_x
В сборке видно, что код сборки не был сгенерирован, sprintfпотому что компилятор предполагал, что xон не изменится вне программы. И то же самое в случае с whileпетлей. whileцикл был полностью удален из - за оптимизации , потому что компилятор видел его как ненужный код и , таким образом , непосредственно закрепленный 5в x(см movl $5, x(%rip)).
Проблема возникает, когда что, если внешний процесс / оборудование изменит значение xгде-то между x = 8;и if(x == 8). Мы ожидаем, что elseблок сработает, но, к сожалению, компилятор обрезал эту часть.
Теперь, чтобы решить эту проблему assembly.cpp, давайте перейдем int x;к volatile int x;и быстро увидим сгенерированный код сборки:
Здесь вы можете увидеть , что сборочные коды sprintf, printfи whileпетли были созданы. Преимущество состоит в том, что если xпеременная изменяется какой-либо внешней программой или оборудованием, sprintfчасть кода будет выполнена. И аналогичным образом whileцикл можно использовать для занятого ожидания сейчас.
Другие ответы уже упоминают об избежании некоторой оптимизации, чтобы:
использовать регистры с отображением памяти (или "MMIO")
написать драйверы устройства
облегчить отладку программ
сделать вычисления с плавающей запятой более детерминированными
Волатильность необходима, когда вам нужно, чтобы значение казалось внешним, непредсказуемым и избегало оптимизаций компилятора на основе известного значения, а также когда результат фактически не используется, но вам нужно, чтобы он вычислялся или использовался, но Вы хотите вычислить его несколько раз для эталонного теста, и вам нужно, чтобы вычисления начинались и заканчивались в точных точках.
Изменчивое чтение похоже на операцию ввода (например, scanfиспользование cin): значение, похоже, приходит извне программы, поэтому любые вычисления, зависящие от значения, должны начинаться после нее .
Энергозависимая запись похожа на операцию вывода (например, printfиспользование cout): значение, по-видимому, передается вне программы, поэтому, если значение зависит от вычисления, его необходимо завершить раньше .
Таким образом, пара нестабильных операций чтения / записи может быть использована для того, чтобы приручить тесты и сделать измерение времени значимым .
Без использования volatile ваши вычисления могут быть запущены компилятором раньше, поскольку ничто не помешает переупорядочению вычислений с такими функциями, как измерение времени .
volatile
можно эффективно использовать, собрав в довольно обыденных терминах. Ссылка: publishing.gbdirect.co.uk/c_book/chapter8/…volatile
полезнее, чемfriend
ключевое слово.Ответы:
volatile
нужен, если вы читаете из области памяти, в которой, скажем, совершенно отдельный процесс / устройство / все, что может записывать.Раньше я работал с двухпортовым ОЗУ в многопроцессорной системе на прямом C. Мы использовали 16-битное значение с аппаратным управлением как семафор, чтобы знать, когда другой парень закончил. По сути, мы сделали это:
Без
volatile
этого оптимизатор считает цикл бесполезным (парень никогда не устанавливает значение! Он сошел с ума, избавьтесь от этого кода!), И мой код продолжит работу, не получив семафор, что впоследствии вызовет проблемы.источник
uint16_t* volatile semPtr
было написано вместо? Это должно пометить указатель как volatile (вместо значения, на которое указывает), так что проверки самого указателя, например,semPtr == SOME_ADDR
могут быть не оптимизированы. Это, однако, также подразумевает волатильное указанное значение. Нет?volatile
необходим при разработке встроенных систем или драйверов устройств, где необходимо читать или записывать отображаемое в память аппаратное устройство. Содержимое конкретного регистра устройства может измениться в любое время, поэтому вам нужноvolatile
ключевое слово, чтобы компилятор не оптимизировал такой доступ.источник
Некоторые процессоры имеют регистры с плавающей запятой, которые имеют точность более 64 бит (например, 32-битный x86 без SSE, см. Комментарий Питера). Таким образом, если вы выполняете несколько операций над числами с двойной точностью, вы на самом деле получаете ответ с более высокой точностью, чем если бы вы усекали каждый промежуточный результат до 64 бит.
Обычно это замечательно, но это означает, что в зависимости от того, как компилятор назначил регистры и выполнил оптимизацию, вы получите разные результаты для точно таких же операций с одинаковыми входными данными. Если вам нужна согласованность, вы можете заставить каждую операцию вернуться в память, используя ключевое слово volatile.
Это также полезно для некоторых алгоритмов, которые не имеют алгебраического смысла, но уменьшают ошибку с плавающей запятой, например, суммирование по Кахану. Алгебраически это не nop, поэтому он часто будет неправильно оптимизирован, если некоторые промежуточные переменные не являются изменчивыми.
источник
volatile double
вместо простойdouble
, чтобы обеспечить ее усечение с точности FPU до 64-битной (RAM) точности перед продолжением дальнейших вычислений. Результаты существенно отличались из-за дальнейшего преувеличения ошибки с плавающей точкой.g++ -mfpmath=sse
использовать его и для 32-битной x86. Вы можете использоватьgcc -ffloat-store
для принудительного округления везде, даже при использовании x87, или вы можете установить точность x87 на 53-битную мантиссу: randomascii.wordpress.com/2012/03/21/… .volatile
для принудительного округления в нескольких конкретных местах, не теряя преимущества везде.Из статьи Дэна Сакса «Волатильно как обещание» :
Вот ссылки на три его статьи относительно
volatile
ключевого слова:источник
Вы ДОЛЖНЫ использовать volatile при реализации структур данных без блокировки. В противном случае компилятор может оптимизировать доступ к переменной, что изменит семантику.
Иными словами, volatile сообщает компилятору, что доступ к этой переменной должен соответствовать операции чтения / записи в физической памяти.
Например, вот как InterlockedIncrement объявляется в Win32 API:
источник
std::atomic<LONG>
вы можете писать код без блокировок более безопасно, без проблем с оптимизацией чистых загрузок / чистых хранилищ, переупорядочением или чем-то еще.Большое приложение, над которым я работал в начале 1990-х годов, содержало обработку исключений на основе C с использованием setjmp и longjmp. Ключевое слово volatile было необходимо для переменных, значения которых нужно было сохранить в блоке кода, который служил в качестве предложения «catch», чтобы эти переменные не сохранялись в регистрах и не стирались longjmp.
источник
В стандарте C одно из мест для использования
volatile
- с обработчиком сигнала. Фактически, в стандарте C все, что вы можете безопасно сделать в обработчике сигналов, это изменитьvolatile sig_atomic_t
переменную или быстро выйти. Действительно, AFAIK, это единственное место в Стандарте C, дляvolatile
которого требуется использование, чтобы избежать неопределенного поведения.Это означает, что в стандарте C вы можете написать:
и не намного больше.
POSIX гораздо более снисходительно относится к тому, что вы можете делать в обработчике сигналов, но есть все еще ограничения (и одно из ограничений заключается в том, что стандартную библиотеку ввода-вывода
printf()
и т. Д. Нельзя безопасно использовать).источник
Разрабатывая для встроенного, у меня есть цикл, который проверяет переменную, которая может быть изменена в обработчике прерываний. Без «volatile» цикл превращается в петлю - насколько может сказать компилятор, переменная никогда не меняется, поэтому она оптимизирует проверку.
То же самое относится и к переменной, которая может быть изменена в другом потоке в более традиционной среде, но там мы часто делаем вызовы синхронизации, поэтому компилятор не так свободен с оптимизацией.
источник
Я использовал его в отладочных сборках, когда компилятор настаивает на оптимизации переменной, которую я хочу видеть при выполнении кода.
источник
Помимо использования по назначению, volatile используется в (шаблонном) метапрограммировании. Его можно использовать для предотвращения случайной перегрузки, поскольку атрибут volatile (например, const) участвует в разрешении перегрузки.
Это законно; Обе перегрузки потенциально могут быть вызваны и почти одинаковы. Состав в
volatile
перегрузке является законным, поскольку мы знаем, что бар всеT
равно не пройдет энергонезависимый .volatile
Версия не строго хуже, хотя, так и не выбрали в разрешении перегрузки , если энергонезависимаяf
доступен.Обратите внимание, что код никогда не зависит от
volatile
доступа к памяти.источник
источник
volatile
Ключевое слово предназначено для предотвращения компилятора применения каких - либо оптимизаций на объектах , которые могут изменить таким образом , что не может быть определенно с помощью компилятора.Объекты, объявленные как
volatile
, исключаются из оптимизации, потому что их значения могут быть изменены кодом вне области текущего кода в любое время. Система всегда считывает текущее значениеvolatile
объекта из области памяти, а не сохраняет его значение во временном регистре в той точке, в которой оно запрашивается, даже если предыдущая инструкция запрашивала значение из того же объекта.Рассмотрим следующие случаи
1) Глобальные переменные, измененные подпрограммой обработки прерываний вне области действия.
2) Глобальные переменные в многопоточном приложении.
Если мы не используем volatile квалификатор, могут возникнуть следующие проблемы
1) Код может не работать должным образом при включенной оптимизации.
2) Код может не работать должным образом, когда прерывания включены и используются.
Изменчивый: лучший друг программиста
https://en.wikipedia.org/wiki/Volatile_(computer_programming)
источник
Помимо того, что ключевое слово volatile используется для указания компилятору не оптимизировать доступ к некоторой переменной (которая может быть изменена потоком или подпрограммой прерывания), оно также может использоваться для удаления некоторых ошибок компилятора - ДА, это может быть ---
Например, я работал над встроенной платформой, где компилятор делал неправильные предположения относительно значения переменной. Если код не был оптимизирован, программа будет работать нормально. С оптимизацией (которая была действительно необходима, потому что это была критическая процедура) код не работал бы правильно. Единственное решение (хотя и не очень правильное) состояло в том, чтобы объявить переменную «неисправный» как volatile.
источник
Кажется, ваша программа работает даже без
volatile
ключевого слова? Возможно, это причинаКак упоминалось ранее,
volatile
ключевое слово помогает в таких случаях, какНо, похоже, эффект почти не проявляется после вызова внешней или не встроенной функции. Например:
Затем с или без
volatile
почти такой же результат генерируется.Пока g () может быть полностью встроенным, компилятор может видеть все, что происходит, и поэтому может оптимизировать. Но когда программа обращается к месту, где компилятор не может видеть, что происходит, компилятору небезопасно делать какие-либо предположения. Следовательно, компилятор будет генерировать код, который всегда читает непосредственно из памяти.
Но остерегайтесь того дня, когда ваша функция g () станет встроенной (либо из-за явных изменений, либо из-за хитрости компилятора / компоновщика), тогда ваш код может сломаться, если вы забудете
volatile
ключевое слово!Поэтому я рекомендую добавить
volatile
ключевое слово, даже если ваша программа работает без него. Это делает намерение более ясным и надежным в отношении будущих изменений.источник
volatile
квалифицированного указателя на функцию:void (* volatile fun_ptr)() = fun; fun_ptr();
В первые дни C компиляторы интерпретировали все действия, которые читают и записывают значения l, как операции с памятью, которые должны выполняться в той же последовательности, в которой операции чтения и записи появились в коде. Во многих случаях эффективность могла бы быть значительно улучшена, если бы компиляторам была предоставлена определенная свобода для переупорядочения и консолидации операций, но с этим была проблема. Даже операции часто указывались в определенном порядке просто потому, что необходимо было указывать их в каком-то порядке, и, таким образом, программист выбрал одну из многих одинаково хороших альтернатив, что не всегда имело место. Иногда было бы важно, чтобы определенные операции происходили в определенной последовательности.
Какие именно детали последовательности важны, зависит от целевой платформы и области применения. Вместо того, чтобы предоставлять особенно подробный контроль, Стандарт выбрал простую модель: если последовательность обращений выполняется с l-значениями, которые не квалифицированы
volatile
, компилятор может переупорядочить и объединить их по своему усмотрению. Если действие выполняется сvolatile
-качественным lvalue, качественная реализация должна предлагать любые дополнительные гарантии упорядочения, которые могут потребоваться при коде, предназначенном для его предполагаемой платформы и области приложения, без необходимости использования нестандартного синтаксиса.К сожалению, вместо того, чтобы определить, какие гарантии понадобятся программистам, многие компиляторы предпочли предложить минимальные гарантии, предусмотренные стандартом. Это делает
volatile
гораздо менее полезным, чем должно быть. Например, в gcc или clang программист, которому необходимо реализовать базовый «мьютекс передачи» [тот, в котором задача, которая получила и освободил мьютекс, не будет делать это до тех пор, пока другая задача не выполнит это], должен выполнить один из четырех вещей:Поместите получение и освобождение мьютекса в функцию, которую компилятор не может встроить и к которой он не может применить Оптимизацию всей программы.
Определите все объекты, охраняемые мьютексом, как
volatile
-что-то, что не должно быть необходимым, если все обращения происходят после получения мьютекса и перед его освобождением.Используйте уровень оптимизации 0 , чтобы заставить компилятор генерировать код , как если бы все объекты, которые не квалифицированы
register
являютсяvolatile
.Используйте специфичные для gcc директивы.
Напротив, при использовании более качественного компилятора, который больше подходит для системного программирования, такого как icc, можно было бы выбрать другой вариант:
volatile
-качественная запись выполняется везде, где требуется приобретение или выпуск.Для получения базового «мьютекса ручной передачи» требуется
volatile
чтение (чтобы увидеть, готово ли оно), и не требуется также иvolatile
запись (другая сторона не будет пытаться повторно получить ее, пока она не будет возвращена), но для этого нужно выполнять бессмысленнуюvolatile
запись все же лучше, чем любой из параметров, доступных в gcc или clang.источник
Я должен напомнить вам, что в функции-обработчике сигналов вы можете использовать глобальную переменную (например, пометить ее как exit = true) и объявить ее как 'volatile'.
источник
Все ответы отличные. Но вдобавок ко всему, я хотел бы поделиться примером.
Ниже приведена небольшая программа cpp:
Теперь давайте сгенерируем сборку из приведенного выше кода (и я вставлю только те части сборки, которые здесь уместны):
Команда для генерации сборки:
И сборка:
В сборке видно, что код сборки не был сгенерирован,
sprintf
потому что компилятор предполагал, чтоx
он не изменится вне программы. И то же самое в случае сwhile
петлей.while
цикл был полностью удален из - за оптимизации , потому что компилятор видел его как ненужный код и , таким образом , непосредственно закрепленный5
вx
(смmovl $5, x(%rip)
).Проблема возникает, когда что, если внешний процесс / оборудование изменит значение
x
где-то междуx = 8;
иif(x == 8)
. Мы ожидаем, чтоelse
блок сработает, но, к сожалению, компилятор обрезал эту часть.Теперь, чтобы решить эту проблему
assembly.cpp
, давайте перейдемint x;
кvolatile int x;
и быстро увидим сгенерированный код сборки:Здесь вы можете увидеть , что сборочные коды
sprintf
,printf
иwhile
петли были созданы. Преимущество состоит в том, что еслиx
переменная изменяется какой-либо внешней программой или оборудованием,sprintf
часть кода будет выполнена. И аналогичным образомwhile
цикл можно использовать для занятого ожидания сейчас.источник
Другие ответы уже упоминают об избежании некоторой оптимизации, чтобы:
Волатильность необходима, когда вам нужно, чтобы значение казалось внешним, непредсказуемым и избегало оптимизаций компилятора на основе известного значения, а также когда результат фактически не используется, но вам нужно, чтобы он вычислялся или использовался, но Вы хотите вычислить его несколько раз для эталонного теста, и вам нужно, чтобы вычисления начинались и заканчивались в точных точках.
Изменчивое чтение похоже на операцию ввода (например,
scanf
использованиеcin
): значение, похоже, приходит извне программы, поэтому любые вычисления, зависящие от значения, должны начинаться после нее .Энергозависимая запись похожа на операцию вывода (например,
printf
использованиеcout
): значение, по-видимому, передается вне программы, поэтому, если значение зависит от вычисления, его необходимо завершить раньше .Таким образом, пара нестабильных операций чтения / записи может быть использована для того, чтобы приручить тесты и сделать измерение времени значимым .
Без использования volatile ваши вычисления могут быть запущены компилятором раньше, поскольку ничто не помешает переупорядочению вычислений с такими функциями, как измерение времени .
источник