В этом примере:
volatile uint32_t * pOne = 0xDEADBEEF;
volatile uint32_t * pTwo = 0x0BADC0DE;
void same(void)
{
uint32_t tmp;
tmp = *pOne; // A
*pOne = 0; // B
*pOne = tmp; // C
}
void different(void)
{
uint32_t tmp;
tmp = *pOne;
*pOne = 0; // E
*pTwo = 0; // F
*pOne = tmp;
}
Насколько мне известно, компилятору C99 не разрешается переупорядочивать строки A, B и C в функции same(), поскольку все они относятся к одному и тому же изменчивому объекту.
Но как насчет линий E и F в функции different()? Они взаимодействуют с разными летучими объектами.
E и F?Мне не удалось найти ответ в самом стандарте, так как раздел 5.1.2.3 меня немного сбивает с толку. Так что если бы вы могли это объяснить, я был бы рад.
Я знаю, что это касается только переупорядочения компилятора и не влияет на переупорядочивание процессором.
Так есть ли стандартная библиотека, которая (если реализована) обеспечивает барьеры памяти?
На данный момент я придерживаюсь C99, но из любопытства: есть ли какие-либо изменения в C11?
Пожалуйста, объясните практическое использование кода. В каком контексте?





Реализации допускаются значительные усмотрения в отношении семантики объектов volatile. Наблюдаемое поведение программы должно согласовываться со всеми операциями со всеми изменчивыми объектами, выполняемыми в указанной последовательности, но реализации имеют полное разрешение делать почти все, что не влияет на наблюдаемое поведение программы. Реализации также имеют довольно широкие полномочия определять - в рамках «поведения, определяемого реализацией» - какие аспекты доступа с изменяемой привязкой являются «наблюдаемыми», а какие - нет.
Соответствующая реализация, которая не предназначена для встроенного программирования, может указывать, что доступ с квалификацией volatile будет вести себя странным и произвольным образом, что сделает его непригодным для таких целей. На большинстве платформ должно быть достаточно ясно, как качественная реализация, которая предназначена для встроенного программирования без использования директив, специфичных для компилятора, должна обрабатывать доступы, соответствующие требованиям volatile, и высококачественные реализации, предназначенные для такого использования, должны обрабатывать их таким образом даже хотя Стандарт не требует от них этого. К сожалению, некоторые разработчики компиляторов ограничивают свою семантику тем, что явно требует Стандарт, вместо того, чтобы расширять их семантику для соответствия базовой платформе (например, при нацеливании на платформы, на которых доступ к volatile может вести себя как вызовы подпрограмм, не меняйте порядок операций через доступ к volatile. если только его нельзя переупорядочить при вызове неизвестной функции). В то время как icc, похоже, обрабатывает записи, соответствующие требованиям volatile, таким образом, компиляторы, такие как gcc и clang, которые не предназначены для использования во встроенных системах с включенной оптимизацией, будут делать это только тогда, когда большинство оптимизаций отключено.
Из спецификации (6.7.3.6)
An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3. Furthermore, at every sequence point the value last stored in the object shall agree with that prescribed by the abstract machine, except as modified by the unknown factors mentioned previously. What constitutes an access to an object that has volatile-qualified type is implementation-defined.
Получение / установка значения будет считаться "доступом" в большинстве, если не во всех, реализациях.
И из 5.1.2.3:
Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression may produce side effects. At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place. (A summary of the sequence points is given in annex C.)
In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).
Каждое присвоение является точкой последовательности, поэтому, если реализация не «делает вывод о том, что никаких необходимых побочных эффектов не возникает» из изменчивого доступа, то это означает, что эти строки не могут быть переупорядочены в реализации, поскольку это не будет иметь той же семантики. значение.
Конечно, многие компиляторы не на 100% соответствуют стандартам.
Здесь вы можете увидеть вывод ассемблера с различными компиляторами: https://godbolt.org/z/b0TNmT.
GCC, Clang и MSVC не изменяют порядок чтения и записи в сборке. (Хотя я не уверен, что это значит для самого исполняемого файла)
C11 имеет
<stdatomic.h>, который обеспечивает настоящие барьеры памяти. Для C99 стандарт действительно гарантирует, что компилятор не может переупорядочитьEиF(потому что 5.1.2.3p2 говорит, что побочные эффекты все должны быть завершены в точке последовательности, и ничего не говорит о том, влияют ли они на один и тот же объект), но это буквально не требует барьера памяти, и компиляторы сильно различаются по тому, как они это реализуют. Если вы используете однопоточную реализацию, которая используетvolatileдля ввода-вывода с отображением в память, стандартных гарантий достаточно, но что-то мне подсказывает, что это не ваш сценарий.