goodbyeera
goodbyeera

Reputation: 3199

Does the volatile qualifier matter in this case?

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

Answers (4)

James Kanze
James Kanze

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

MattD
MattD

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

Dusty Campbell
Dusty Campbell

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

qehgt
qehgt

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

Related Questions