Rectangle 27 1

c++ What Rules does compiler have to follow when dealing with volatile memory locations?


@DavidRodrguez-dribeas: Interesting notion, though the only case where I can imagine it as being relevant would be if a loop read or wrote a volatile variable some number of times; if such reads or writes have to be performed in sequence, no code after the loop could have any observable side-effects until the processor had performed the appropriate number of reads or writes. Such a thing might be better accomplished by having the standard specify a __SIDE_EFFECT() macro which would compel the compiler to do whatever was necessary to ensure that the code around it was executed in sequence.

Accessing an object designated by a volatile lvalue (3.10), modifying an object, calling a library I/O function, 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 might 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.

An instance of each object with automatic storage duration (3.7.2) is associated with each entry into its block. Such an object exists and retains its last-stored value during the execution of the block and while the block is suspended (by a call of a function or receipt of a signal).

I can see the logic, but that's leaning a bit too hard on the as-if rule if you ask me.

It's not as well defined as you probably want it to be. Most of the relevant standardese from C++98 is in section 1.9, "Program Execution":

Note that some known experts (Herb Sutter in particular) mention that a smart compiler can even treat a volatile variable as non volatile if it can demonstrate that it cannot be read externally: think on a variable declared volatile but not bound to a particular address, for which no pointers/references are passed to other code --example: an auto volatile variable, a conforming compiler can treat it as non-volatile.

Once the execution of a function begins, no expressions from the calling function are evaluated until execution of the called function has completed.

So what that boils down to is:

The least requirements on a conforming implementation are:

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

When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects with type other than volatile sig_atomic_t are unspecified, and the value of any object not of volatile sig_atomic_t that is modified by the handler becomes undefined.

Note
Rectangle 27 1

c++ What Rules does compiler have to follow when dealing with volatile memory locations?


A particular and very common optimization that is ruled out by volatile is to cache a value from memory into a register, and use the register for repeated access (because this is much faster than going back to memory every time).

Instead the compiler must fetch the value from memory every time (taking a hint from Zach, I should say that "every time" is bounded by sequence points).

Nor can a sequence of writes make use of a register and only write the final value back later on: every write must be pushed out to memory.

Why is this useful? On some architectures certain IO devices map their inputs or outputs to a memory location (i.e. a byte written to that location actually goes out on the serial line). If the compiler redirects some of those writes to a register that is only flushed occasionally then most of the bytes won't go onto the serial line. Not good. Using volatile prevents this situation.

Note
Rectangle 27 1

c++ What Rules does compiler have to follow when dealing with volatile memory locations?


int *i = ...;
cout << *i; // line A
// ... (some code that doesn't use i)
cout << *i; // line B

Declaring a variable as volatile means the compiler can't make any assumptions about the value that it could have done otherwise, and hence prevents the compiler from applying various optimizations. Essentially it forces the compiler to re-read the value from memory on each access, even if the normal flow of code doesn't change the value. For example:

In this case, the compiler would normally assume that since the value at i wasn't modified in between, it's okay to retain the value from line A (say in a register) and print the same value in B. However, if you mark i as volatile, you're telling the compiler that some external source could have possibly modified the value at i between line A and B, so the compiler must re-fetch the current value from memory.

Note
Rectangle 27 1

c++ What Rules does compiler have to follow when dealing with volatile memory locations?


class MyGizmo
{ 
public:
  const Foo* foo_;
};
gizmo.foo_->bar_ = 42;

...the compiler won't allow it, because it's marked const. Obviously you can get around this by using const_cast to cast away the const-ness, but if you need to be convinced this is a bad idea then there is no help for you. :)

@Pooria: Here: "when reading from a location of memory which is written to by several threads or processes the volatile keyword should be used for that location" This assertion is absolutely false. volatile does nothing for you here other than give you a false sense of security.

@Pooria: I can only hope that your defensive attitude and refusal to answer my questions is an indication that you are offended because you've learned that you were wrong. For what it's worth, I always read everything that everyone has to say. That's how I learn.

@Pooria: In your linked question, the accepted answer deals specifically with memory-mapped hardware. If you had read my post, you will have noted that a) I specifcally say that's what volatile is for, and b) even that has nothing to do with multithreadded programming. It has to do with hardware access.

@Pooria: In your very first sentence.

Alexandrescu's use of volatile is exactly the same. It doesn't do anything to make the memory somehow "thread safe" in any way whatsoever. What it does is it gives the compiler another way to let you know when you may have screwed up. You mark things that you have made truly "thread safe" (through the use of actual synchronization objects, like Mutexes or Semaphores) as being volatile. Then the compiler won't let you use them in a non-volatile context. It throws a compiler error you then have to think about and fix. You could again get around it by casting away the volatile-ness using const_cast, but this is just as Evil as casting away const-ness.

EDIT: I'll try to elaborate a little bit on what I just said.

It can also be used in a similar way that const is used, and this is how Alexandrescu uses it in this article. But make no mistake. volatile doesn't make your code magically thread safe. Used in this specific way, it is simply a tool that can help the compiler tell you where you might have messed up. It is still up to you to fix your mistakes, and volatile plays no role in fixing those mistakes.

My advice to you is to completely abandon volatile as a tool in writing multithreadded applications (edit:) until you really know what you're doing and why. It has some benefit but not in the way that most people think, and if you use it incorrectly, you could write dangerously unsafe applications.

Suppose you have a class that has a pointer to something that cannot change. You might naturally make the pointer const:

What does const really do for you here? It doesn't do anything to the memory. It's not like the write-protect tab on an old floppy disc. The memory itself it still writable. You just can't write to it through the foo_ pointer. So const is really just a way to give the compiler another way to let you know when you might be messing up. If you were to write this code:

What volatile is used for is interfacing with memory-mapped hardware, signal handlers and the setjmp machine code instruction.

What you know is false. Volatile is not used to synchronize memory access between threads, apply any kind of memory fences, or anything of the sort. Operations on volatile memory are not atomic, and they are not guaranteed to be in any particular order. volatile is one of the most misunderstood facilities in the entire language. "Volatile is almost useless for multi-threadded programming."

Note