An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Asynchronous signal handling falls into this category, for example, may cause objects to be modified in a manner unknown to the compiler. Without this type qualifier, unintended optimizations may occur. These optimizations may cause race conditions, because a programmer may write code that prevents a race condition, yet the compiler is not aware of the programmer's data model, and may modify the code during cmopilation compilation to permit race conditions.
...
No caching through this lvalue: each operation in the abstract semantics must be performed (that is, no cacheing assumptions may be made, since because the location is not guaranteed to contain any previous value). In the absence of this qualifier, the contents of the designated location may be assumed to be unchanged except for possible aliasing.
Please keep in mind that while adding volatile will ensure While type qualifying objects as volatile ensures that a compiler does not perform unintended reordering or optimization, it in no way guarantees synchronization between multiple threads, ward against simultaneous memory accesses, and unless used to declare objects of type sig_atomic_t guarantee atomicity of accesses to the object. For restrictions specific to signal handlers see SIG31-C. Do not access or modify shared objects in signal handlers.
...
This noncompliant code example relies on the reception of a SIGINT signal to toggle a flag to terminate a loop. However, since because interrupted is not declared volatile, the read from it in main() may be optimized away by the compiler despite the assignment to the variable in the signal handler and the loop may never terminate. When compiled on GCC with the -O optimization flag, for example, the program fails to terminate even upon receiving a SIGINT.
...
Noncompliant Code Example (Cast to volatile)
In the following this noncompliant code example, the function thread_func runs in the context of multiple threads designed to communicate with one another via the global variable end_processing. The function attempts to prevent the compiler from optimizing away the while loop condition by casting the variable to volatile before accessing it. However, since because end_processing is not declared volatile, the assignment to it in the body of the loop need not be flushed from a register into memory and thus may not be visible when read despite the cast. As a result, the loop may never terminate.
Note, however, that declaring an object volatile is not sufficient to prevent race conditions when the object is simultaneously accessed from within two or more threads of execution. Additional memory visibility constraints may necessitate the use of platform-specific constructs such as memory barriers, for example when each of the threads runs on a different processor.
...