views:

301

answers:

3

A compiler cannot eliminate or reorder reads/writes to a volatile-qualified variables.

But what about the cases where other variables are present, which may or may not be volatile-qualified?

Scenario 1

volatile int a;
volatile int b;

a = 1;
b = 2;
a = 3;
b = 4;

Can the compiler reorder first and the second, or third and the fourth assignments?

Scenario 2

volatile int a;
int b, c;

b = 1;
a = 1;
c = b;
a = 3;

Same question, can the compiler reorder first and the second, or third and the fourth assignments?

A: 

For scenario 1, the compiler should not perform any of the reorderings you mention. For scenario 2, the answer might depend on:

  • and whether the b and c variables are visible outside the current function (either by being non-local or having had their address passed
  • who you talk to (apparently there is some disagreement about how string volatile is in C/C++)
  • your compiler implementation

So (softening my first answer), I'd say that if you're depending on certain behavior in scenario 2, you'd have to treat it as non-portable code whose behavior on a particular platform would have be determined by whatever the implementation's documentation might indicate (and if the docs said nothing about it, then you're out of luck with a guaranteed behavior.

from C99 5.1.2.3/2 "Program execution":

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.

...

(paragraph 5) The least requirements on a conforming implementation are:

  • At sequence points, volatile objects are stable in the sense that previous accesses are complete and subsequent accesses have not yet occurred.

Here's a little of what Herb Sutter has to say about the required behavior of volatile accesses in C/C++ (from "volatile vs. volatile" http://www.ddj.com/hpc-high-performance-computing/212701484) :

what about nearby ordinary reads and writes -- can those still be reordered around unoptimizable reads and writes? Today, there is no practical portable answer because C/C++ compiler implementations vary widely and aren't likely to converge anytime soon. For example, one interpretation of the C++ Standard holds that ordinary reads can move freely in either direction across a C/C++ volatile read or write, but that an ordinary write cannot move at all across a C/C++ volatile read or write -- which would make C/C++ volatile both less restrictive and more restrictive, respectively, than an ordered atomic. Some compiler vendors support that interpretation; others don't optimize across volatile reads or writes at all; and still others have their own preferred semantics.

And for what it's worth, Microsoft documents the following for the C/C++ volatile keyword (as Microsoft-sepcific):

  • A write to a volatile object (volatile write) has Release semantics; a reference to a global or static object that occurs before a write to a volatile object in the instruction sequence will occur before that volatile write in the compiled binary.

  • A read of a volatile object (volatile read) has Acquire semantics; a reference to a global or static object that occurs after a read of volatile memory in the instruction sequence will occur after that volatile read in the compiled binary.

This allows volatile objects to be used for memory locks and releases in multithreaded applications.

Michael Burr
As observed from the same thread. The standard doesn't say anything about when the changes can be visible to other tasks. But even the quote you give in your answer says that _volatile_ objects are stable, there are no guarantees concerning other, non-volatile, accesses.
Ben Voigt
Volatile objects are stable; it is not the case that all objects are stable when a volatile object is accessed.
Potatoswatter
My interpretation of the first paragraph is that only the first case is ruled out. It says "all side effects of previous evaluations shall be complete", but does not mention "non-side-effects": local non-volatile variables reads/writes, which is not considered side effects according to this definition.
Alex B
At the sequence point which follows `c = b`, the side effect of `c = b` must be complete, *in the abstract machine*, but this is still subject to the "as-if" rule. Whether `c` has actually been assigned or not in the emitted machine code is not observable behavior in the example code, so an implementation is free to reorder it (or eliminate it entirely, since `c` is unused). If there were a function call to unknown code that might have a reference to `c`, after `c = b` but before `a = 3`, then the value of `c` "at that sequence point" potentially affects observable behavior.
Steve Jessop
Regarding your edit: it gets a bit more exciting if the implementation supports threading as an extension, and wants to use `volatile` as part of its multi-threaded memory model, which is what Microsoft does. C++03 of course doesn't care about any of that. I'm curious to know whether Sutter reads the standard to permit `c` to be optimized away entirely, but not to permit the write to it to be reordered across the write to `a`. If so, what's the point? If not is he saying `c` can't be omitted? Or that his interpretation doesn't apply to this case for some reason.
Steve Jessop
Based on feedback, I've softened my answer regarding scenario 2.
Michael Burr
+1  A: 

Volatile is not a memory fence. Assignments to B and C in snippet #2 can be eliminated or performed whenever. Why would you want the declarations in #2 to cause the behavior of #1?

Potatoswatter
+5  A: 

The C++ standard says (1.9/6):

The observable behavior of the abstract machine is its sequence of reads and writes to volatile data and calls to library I/O functions.

In scenario 1, either of the changes you propose changes the sequence of writes to volatile data.

In scenario 2, neither change you propose changes the sequence. So they're allowed under the "as-if" rule (1.9/1):

... conforming implementations are required to emulate (only) the observable behavior of the abstract machine ...

In order to tell that this has happened, you would need to examine the machine code, use a debugger, or provoke undefined or unspecified behavior whose result you happen to know on your implementation. For example, an implementation might make guarantees about the view that concurrently-executing threads have of the same memory, but that's outside the scope of the C++ standard. So while the standard might permit a particular code transformation, a particular implementation could rule it out, on grounds that it doesn't know whether or not your code is going to run in a multi-threaded program.

If you were to use observable behavior to test whether the re-ordering has happened or not (for example, printing the values of variables in the above code), then of course it would not be allowed by the standard.

Steve Jessop
Why does the reordering in 2.1 change the sequence? Only one assignment is to a volatile variable?
Alex B
Sorry, my mistake.
Steve Jessop
Glad somebody understands `volatile` +1
Norman Ramsey