Reordering of access to multiple volatile variables in C

1

In this example:

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;
}

As far as I know, a C99 compiler is not allowed to reorder the lines A, B and C in function same(), since they all refer to the same volatile object.
But how about the lines E and F in function different()? They interact with different volatile objects.

  1. Is a C99 compiler allowed to reorder lines E and F?

I was not able to find the answer in the standard itself, since section 5.1.2.3 is a bit confusing to me. So if you could explain this, I would be glad.

I'm aware that this only concerns the reordering of the compiler, and does not affect any reordering by the processor.

  1. So is there a standard library, that (if implemented) provides Memory barriers?

  2. At the moment I am stuck to C99, but out of curiosity: Are there any changes in C11?

c
language-lawyer
c99
volatile
order-of-execution
asked on Stack Overflow Sep 3, 2018 by HeLLoTIS • edited Sep 5, 2018 by Deduplicator

2 Answers

0

Implementations are allowed considerable discretion with regard to the semantics of volatile objects. The observable behavior of a program must be consistent with all operations upon all volatile objects having been performed in the specified sequence, but implementations have blanket permission to do almost anything that doesn't affect a program's observable behavior. Implementations also have rather broad permission to specify--within the realm of "Implementation Defined Behavior"--what aspects of volatile-qualified accesses are and are not "observable".

A conforming implementation which is not intended to be suitable for embedded programming could specify that volatile-qualified accesses will behave in weird and arbitrary ways that would make it unusable for such purposes. On most platforms, it should be fairly clear how quality implementation which are intended to allow embedded programming without the use of compiler-specific directives should process volatile-qualified accesses, and high-quality implementations intended for such use should process them in such fashion even though the Standard does not require them to do so. Unfortunately, some compiler writers limit their semantics to that the Standard explicitly demands, rather than extending their semantics to match the underlying platform (e.g. when targeting platforms where a volatile accesses could behave like subroutine calls, don't reorder any operation across a volatile access unless it could be reordered across a call to an unknown function). While icc seems to treat volatile-qualified writes in such fashion, compilers like gcc and clang which aren't designed to be suitable for embedded systems use with optimizations enabled will only do so when most optimizations are disabled.

answered on Stack Overflow Sep 4, 2018 by supercat
0

From the spec (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.

Getting / setting the value will count as "access" in most, if not all, implementations.

And from 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).

Every assignment is a sequence point, so if an implementation does not "deduce that no needed side effects are produced" from the volatile access, then that means those lines can't be reordered in an implementation, as that would not have the same semantic meaning.

Of course, many compilers are not 100% standards compliant.

Here you can see the assembler output with various compilers: https://godbolt.org/z/b0TNmT .

GCC, Clang and MSVC both do not reorder the reads and writes in assembly. (Though I'm not sure what that means for the actual executable)

answered on Stack Overflow Sep 5, 2018 by Artyer

User contributions licensed under CC BY-SA 3.0