Reputation: 3199
I saw a code sample demonstrating the usage of the volatile
qualifier in the answer of the question C++ volatile member functions, quoted as below:
volatile int x;
int DoSomething() {
x = 1;
DoSomeOtherStuff();
return x+1; // Don't just return 2 because we stored a 1 in x.
// Check to get its current value
}
I don't know if the volatile
qualifier makes any difference in the above code. x
is a global variable and there is a function call between the write and read on x
, and we only read x
for once. Shouldn't the compiler perform a real read on x
(even it's not volatile
)?
I think it's different from the following case:
volatile int x;
int DoSomething() {
x = 1;
while (x != 1)
break;
}
In this case, we repeatedly read x
immediately after a write to x
, so volatile
is necessary to get the up-to-date value of x
written by other threads.
I'm not very confident on my understanding of these code samples, please correct me if I'm wrong.
EDIT (respond to comments): I'm sorry I didn't make my question clear. As for the first code snippet, I'm questioning whether the code is a valid example for the possible usage of volatile
(not the guaranteed usage of volatile). I just want to know, without volatile
, whether it's guaranteed that any possible change to x
in DoSomeOtherStuff()
can be reflected in return x+1
, assuming there's no multi-threading or other non-trivial things like memory mapped IO. Cause if it is guaranteed to work without volatile
, then the example is way off relevant, not to even mention the platform-dependent nature of volatile
as some comments pointed out. But if it's not guaranteed, then I'm afraid some of my existing code may not work as I expected.
(I probably shouldn't have put the second code snippet at all.)
Upvotes: 3
Views: 496
Reputation: 153899
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).
Upvotes: 3
Reputation: 380
The only way to really be sure here is to examine the assembly code generated for your target platform. If volatile is performing the way you intend, any read of the variable x will be accomplished via a load from memory.
Upvotes: 0
Reputation: 3156
Here is an existing SO question about volatile: What is the use of volatile keyword?
With just the first code snippet you are right, there is no obvious reason to have x declared volatile. However, if you understand what the keyword is supposed to be used for you could reason that x is volatile because there is something outside this code, which may change it's value. For example the memory maybe attached to some other hardware or written to by another program. Therefore, the programmer is instructing the compiler that it cannot see all the possible ways for the value of x to change. And so the compiler may not be able to optimize the code in certain ways.
The second code snippet in and of itself does not require the volatile keyword. Volatile is used as a compiler hint stating that the memory could change by forces outside the current program. It should not be used for thread communication. There are new C++ types that should be used for those situations.
I recommend reading this article by Herb Sutter as well as this talk.
Upvotes: 0
Reputation: 2990
A compiler can have some information about DoSomeOtherStuff()
function. For example:
static int DoSomeOtherStuff()
{
return 42;
}
volatile int x;
int DoSomething() {
x = 1;
DoSomeOtherStuff();
return x+1; // Don't just return 2 because we stored a 1 in x.
// Check to get its current value
}
GCC with -O3
option completely removes the call of DoSomeOtherStuff()
function (and its body as well) but still reloads x
to return x+1
in the end.
Upvotes: 0