Во-первых, исторически существовали различные отклонения в отношении разных толкований значения доступа volatile
и тому подобного. См. Это исследование: Неправильные компиляции и что с этим делать .
Помимо различных проблем, упомянутых в этом исследовании, поведение volatile
является переносимым, за исключением одного аспектаиз них: когда они действуют как барьеры памяти . Барьер памяти - это некоторый механизм, который предотвращает одновременное неупорядоченное выполнение вашего кода. Использование volatile
в качестве барьера памяти, безусловно, не переносимо.
1011 * Является ли язык гарантирует поведение памяти C или не из
volatile
по-видимому, спорно, хотя лично я считаю, что язык понятен. Во-первых, у нас есть формальное определение побочных эффектов, C17 5.1.2.3:
Доступ к объекту volatile
, изменение объекта, изменение файла или вызов функции, выполняющей любую из этих операций:все побочные эффекты , которые являются изменениями в состоянии среды выполнения.
Стандарт определяет термин «последовательность» как способ определения порядка оценки (выполнения). Определение является формальным и громоздким:
Последовательность перед является асимметричным, транзитивным, попарным отношением между оценками, выполняемыми одним потоком, что вызывает частичный порядок среди этих оценок,При любых двух оценках A и B, если A секвенируется перед B, тогда выполнение A должно предшествовать выполнению B. (И наоборот, если A секвенируется до B, тогда B секвенируется после A. ) Если A не секвенируется до или после B, то A и B не секвенированы . Оценки A и B являются неопределенно упорядоченными , когда A упорядочивается до или после B, но это не определено, что. 13) Наличие точки последовательности между оценками выражений A иB подразумевает, что каждое вычисление значения и побочный эффект, связанный с A, упорядочивается перед каждым вычислением значения и побочным эффектом, связанным с B. (Краткое описание точек последовательности приведено в приложении C.)
TL; DR вышеупомянутого в основном состоит в том, что в случае, если у нас есть выражение A
, которое содержит побочные эффекты, это должно быть выполнено перед другим выражением B
, в случае, если B
секвенируется после A
.
Оптимизация кода на C возможна благодаря этой части:
В абстрактной машине все выражения оцениваются в соответствии с семантикой. Реальная реализация не должна оценивать часть выражения, если она может сделать вывод, что его значение не используется и что не возникает никаких побочных эффектов (включая любые, вызванные вызовом функции или доступом к энергозависимому объекту).
Это означает, что программа может оценивать (выполнять) выражения в порядке, который стандарт предписывает в другом месте (порядок оценки и т. Д.). Но ему не нужно оценивать (выполнять) значение, если оно может сделать вывод, что оно не используется. Например, для операции 0 * x
не нужно оценивать x
и просто заменить выражение на 0
.
Если доступ к переменной не является побочным эффектом. Это означает, что в случае x
равно volatile
, оно должно оценить (выполнить) 0 * x
, даже если результат всегда будет равен 0. Оптимизация не допускается.
Кроме того,Стандарт говорит о наблюдаемом поведении:
Минимальные требования к соответствующей реализации:
- Доступ к изменчивым объектам оценивается строго в соответствии с правилами абстрактной машины.
/ - / Это наблюдаемое поведение программы.
Учитывая все вышеизложенное, соответствующая реализация (компилятор + базовая система) может не выполнять доступ к volatile
объектам в неупорядоченном порядке, если семантика письменного источника C говорит об обратном.
Это означает, что в этом примере
volatile int x;
volatile int y;
z = x;
z = y;
Оба выражения присваивания должны быть оценены и z = x;
должны быть оценены до z = y;
. Многопроцессорная реализация, которая переносит эти две операции на два разных непоследовательных ядра, не соответствует!
Дилемма состоит в том, что компиляторы не могут многое сделать в таких вещах, как предварительное выборочное кэширование, конвейерная обработка команд и т. Д., Особенно когдаработает поверх ОС. И поэтому компиляторы передают эту проблему программистам, говоря им, что барьеры памяти теперь являются обязанностью программиста. Хотя в стандарте C четко указано, что проблема должна решаться компилятором.
Хотя компилятору не обязательно решать эту проблему, и поэтому volatile
ради действия в качестве барьера памятине является переносимымЭто стало вопросом качества реализации.