This is a normal scenario of an object being shared between two threads, one thread is updating its value and the other is waiting for the updated values. The above code seems harmless but there is something wrong with it. It will not produce the desired results. We could try to make everything in the structure volatile but this would produce inefficient code. We don’t want to lose efficiency; we just want to share data between the two threads. In this article, I will show you what problems can be caused by the above code, and why should we avoid the solution of placing volatile with everything.

This article will have two parts. The first part will try to resolve all the confusion surrounding volatile. We will discuss its semantics: declaration and assignments, its use in a multi-threaded environment and its use in a kernel setting. The second part will lay out use cases where volatile is considered necessary like in a setjmp and longjmp, signal handling and inline assembly.

My motivation for writing this article is to make some sense of the chaos surrounding volatile. I wanted this article to be a complete guide for the use of volatile. But from what I have researched, it’s not that we don’t know how to use this keyword, it’s just that we don’t know when to stop using it. The basic use cases, which are as follows, are well known.

Use volatile on memory mapped IO

Use volatile on global data shared between multiple tasks

Use volatile on data used in ISR.

A lot has been written on them, and it is because of this reiteration of these basic use cases that we tend to start using volatile everywhere. All of the code I see seems to lie in one of the above categories. For example I was faced with a problem related to global data accessed in an ISR. I placed volatile with everything I thought could cause the problem and the code worked fine. But as we will see later, this solution hid the problem as opposed to solving it. Naturally an issue was reported after a few months regarding loss of data synchronization.

"The purpose of volatile is to force an implementation to suppress optimization that could otherwise occur. For example, for a machine with memory-mapped input/output, a pointer to a device register might be declared as a pointer to volatile, in order to prevent the compiler from removing apparently redundant references through the pointer." —Dennis Ritchie.

Volatile was introduced to inform the compiler of special memory mapped regions. There is a usenet group post that says that before volatile, compilers had to use strange heuristics to identify which regions were memory mapped and which were not. So volatile was a necessary addition to the C language at the time. But beyond this basic use case, volatile is widely misused.

Back in those days most processors were single core and executed the instructions in-order. What you wrote and the order you wrote it in got executed without a scratch. Nowadays the code that we write, known as abstract machine by the C standard, is a lot different from the actual implementation that gets executed. In fact according to the C standard the only thing the compiler has to make sure to produce executable code is the end result.

The only thing the compiler cannot do is remove or reorder accesses to volatile qualified objects (C-99 rationale). But it can freely remove or reorder non-volatile accesses around it. The compiler can also upgrade some objects to become volatile qualified after certain expressions. Therefore understanding the usage of volatile in basic use cases like declarations and assignments is also necessary. Some developers still struggle to differentiate between a volatile object and a volatile pointer. In the next section I will start with addressing these points of confusions one by one. They are arranged in order of their increasing confusion regarding the usage of volatile.