The volatile
qualifier never makes a difference in the meaning
of the code itself. Unless the compiler can prove that
DoSomeOtherStuff()
doesn't modify x
, it must reread x
regardless, volatile
or no. For volatile
to be relevant,
x
would have to be something like memory mapped IO, which
might change outside the program. If we imagine that it is
a register which is incremented every microsecond, for example:
int
MeasureExecutionTime()
{
x = 0;
DoSomeOtherStuff();
return x;
}
would return the amount of time used in DoSomeOtherStuff
; the
compiler would be required to reload it, even if it inlined
DoSomeOtherStuff
, and saw that it never modified x
.
Of course, on a typical desktop machine, there probably isn't
any memory mapped IO, and if there is, it's in protected memory,
where you cannot access it. And a lot of compilers don't really
generate the code necessary to make it work correctly anyway.
So for general purpose applications on such machines, there's
really no sense in every using volatile
.
EDIT:
Concerning your second code snippet: as usually implemented,
volatile
does not guarantee that you get an up-to-date copy of
x
. Arguably, this is not conform with the intent of
volatile
, but it is the way g++, Sun CC, and at least some
versions of VC++ work. The compilers will issue a load
instruction to read x
each time in the loop, but the hardware
may find the value already in the pipeline, and not propagate
that read request out to the memory bus. In order to guarantee
a new read, the compiler would have to insert a fence or
a membar instruction.
Perhaps more importantly (since sooner or later, something will
happen so that the value won't be in the pipeline), this looping
mechanism will be used to wait until other values written in the
other thread have stabilized. Except that volatile has no
effect on when the reads and writes of other variables occur.
To understand volatile
, it's important to understand the
intent with which it was introduced. When it was introduced,
memory pipelines and such were unknown, and the (C) standard
ignored threading (or multiple processes with shared memory).
The intent of volatile
was to allow support of memory mapped
IO, where writing to an address had external consequences, and
successive reads wouldn't always read the same thing. There was
never any intent that other variables in the code would be
synchronized. When communicating between threads, it's normally
the order of reads and writes, to all shared variables, which
is important. If I do something like:
globalPointer = new Xxx;
and other threads can access globalPointer
, it's important
that all of the writes in the constructor of Xxx
have become
visible before the change in the value of globalPointer
occurs. For this to occur, not only would globalPointer
have
to be volatile
, but also all of the members of Xxx
, and any
variables member functions of Xxx
might use, or any data
accessible through a pointer in Xxx
. This simply isn't
reasonable; you would very soon end up with everything in the
program volatile
. And even then, it would also require that
compilers implement volatile
correctly, isserting fence or
membar instructions around each access. (FWIW: a fence or
a membar instruction can multiply time it takes for a memory
access by a factor of 10 or more.)
The solution here is not volatile, but to make accesses to the
pointer (and only accesses to the pointer) atomic, using the
atomic_load
and atomic_store
primitives added to C++11.
These primitives do cause the necessary fence or membar
instructions to be used; they also tell the compiler not to move
any memory access accross them. So that using atomic_load
to
set the pointer, above, will cause all preceding memory writes
to be visible to other threads before the write to the pointer
becomes visible (on the condition that the reading thread uses
atomic_read
, of course—atomic_write
ensures that all
previous writes are available in the "common" memory of all of
the threads, and atomic_read
ensures that all following reads
will go to the "common" memory, and not pick up some value
already in the pipeline).