В первые дни C компиляторы интерпретировали все действия, которые читают и записывают l-значения, как операции памяти, которые должны выполняться в той же последовательности, в которой операции чтения и записи появлялись в коде. Во многих случаях эффективность могла бы быть значительно улучшена, если бы компиляторам была предоставлена определенная свобода для переупорядочения и консолидации операций, но с этим была проблема. Даже операции часто указывались в определенном порядке только потому, что было необходимо указывать их в некотором порядке, и, таким образом, программист выбрал одну из многих одинаково хороших альтернатив, что не всегда было так. Иногда было бы важно, чтобы определенные операции происходили в определенной последовательности.
Какие именно детали последовательности важны, зависит от целевой платформы и области применения. Вместо того, чтобы предоставлять особенно подробный контроль, Стандарт выбрал простую модель: если последовательность обращений выполняется с l-значениями, которые не квалифицированы volatile
, компилятор может переупорядочить и консолидировать их по своему усмотрению. Если действие выполняется с volatile
-качественным значением lvalue, качественная реализация должна предлагать любые дополнительные гарантии упорядочения при коде, нацеленном на его предполагаемую платформу и поле приложения, без необходимости использования нестандартного синтаксиса. *
К сожалению, вместо того, чтобы определять, какие гарантии понадобятся программистам, многие компиляторы предпочли вместо этого предложить минимальные гарантии, предусмотренные Стандартом. Это делает volatile
гораздо менее полезным, чем должно быть. Например, в gcc или clang программист, которому нужно реализовать базовый «мьютекс передачи» [тот, в котором задача, которая получила и освободил мьютекс, не будет делать это до тех пор, пока другая задача не выполнит это], должен выполнить из четырех вещей:
Поместите получение и освобождение мьютекса в функцию, которую компилятор не может встроить и к которой он не может применить Оптимизацию всей программы.
Определите все объекты, охраняемые мьютексом, как volatile
- то, что не нужно, если все обращения происходят после получения мьютекса и перед его освобождением.
Используйте уровень оптимизации 0, чтобы заставить компилятор генерировать код, как если бы все объекты, которые не квалифицированы register
, были volatile
.
Используйте директивы, специфичные для gcc.
В отличие от этого, при использовании более качественного компилятора, который больше подходит для системного программирования, такого как icc, можно выбрать другой вариант:
- Убедитесь, что
volatile
-качественная запись выполняется везде, где требуется приобретение или выпуск.
Для получения базового «мьютекса передачи» требуется чтение volatile
(чтобы увидеть, готово ли оно), и не требуется также запись volatile
(другая сторона не будет пытаться повторно получить пока он не будет возвращен), но выполнение бессмысленной записи volatile
все же лучше, чем любой из параметров, доступных в gcc или clang.