CERT
Skip to end of metadata
Go to start of metadata

This guideline has not been reviewed recently and may be outdated. Please review it and comment to reflect any newly available information.

There is the misconception that the volatile qualifier will provide the desired properties of a multithreaded program, which are:

  • Atomicity: indivisible memory operations
  • Visibility: the effects of a write action by a thread are visible by other threads
  • Ordering: Sequences of memory operations by a thread are guaranteed to be seen in the same order by other threads

Unfortunately volatile does not provide guarantees for any these. Neither by definition nor by the way it is implemented in various platforms.

The following is the standard's definition of volatile:

C++ standard definition
p.34 SS1.9Accessing 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.

p.138volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation...... In general, the semantics of volatile are intended to be the same in C + + as they are in C.

C99 standard
p.25Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects,11) which are changes in the state of the execution environment. Evaluation of an expression may 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.

In a nutshell, all this keyword does is to inform the compiler that this variable may change in ways that cannot be determined by the compiler and so not to perform optimizations in these memory areas marked as volatile, i.e., not to store the value in a register and use the register instead of doing expensive memory accesses. This concept is much related to multithreading, since if a shared variable is cached, a thread may change it and the other threads may read stale data. Although earlier C++ standards did not address thread issues, the latest describes how the library should support threads. In the case of dangerous optimizations such as these, the C++ standard notes:

ISO/IEC JTC 1/SC 22 N 4411p. 27 of 1314
"Compiler transformations that introduce assignments to a potentially shared memory location that would not be modified by the abstract machine are generally precluded by this standard, since such an assignment might overwrite another assignment by a different thread incases in which an abstract machine execution would not have encountered a data race. This includes implementations of data member assignment that overwrite adjacent members in separate memory locations. We also generally preclude reordering of atomic loads in cases in which the atomics in question may alias, since this may violate the "visible sequence" rules."

Noncompliant Code Example

In the following example, if the flag is declared as volatile, the flag still should not be used as a syncronization primitive in order to modify an account balance.

The above noncompliant example is similar to an example described by Alexandrescu in his article Volatile: A programmer's best friend. Although this article is very good, the initial example is similar to the above and causes confusion to the multithreading novice.

Someone would note that if the variable flag is not declared volatile, then if we compile with optimizations on, the compiler may cache the flag in a register, and as a result the thread that sleeps may never escape the while loop because the change of the flag to true by another thread will not be reflected in the register (the registers and stack are not shared among threads, so if the memory contents are not updated a thread may read the old value).

The problem with this thinking is not realizing that by changing the declaration of the flag to volatile bool flag we only ensure that the compiler generates code to reload a data item each time it is referenced in our program. Although the problem of reading staled data is solved, the above example is not the correct way to implement multithreading, since the volatile keyword does not guarantee for all the attributes required for safe multithreading.

Firstly by declaring a variable as volatile although the volatile qualifier guarantees that the reads and writes will happen in the exact order specified in the source code, the compiler may generate code which reorders a volatile read or write with non-volatile reads or writes, thus limiting its usefulness as an interthread flag or mutex.

Noncompliant Code Example

Let's assume that this trivial code is shared among threads and done is a variable that when set indicates that the thread has finished updating the shared buffer.

In this example, the compiler can change the ordering, and as a result we may first have the assignment 1 to the variable done and then the update of message. This is of course solved by declaring buffer to be volatile as well (and maybe all variables in the program just to be safe???), but that is beside the point. Moreover, it is not guaranteed that volatile reads and writes will be seen in the same order by other processors due to caching, meaning volatile variables may not even work as interthread flags or mutexes.

Concerning the use of volatile, David Butenhof, the author of Programming with POSIX threads, says:

"Don't ever use the C/C++ language volatile in threaded code. It'll kill your performance, and the language definition has nothing to do with what you want when writing threaded code that shares data."

In fact Andrei Alexandrescu, in his article Volatile: A programmer's best friend," states a different opinion, more moderate than Butenhof's, and gives an example of a LockingPtr which shows that the semantics of volatile do not apply inside a critical section and cast_const's away the volatileness and notes:

"Don't forget: volatile equals free multithreaded code and no critical section; non-volatile equals single-threaded scenario or inside a critical section."

Most of the aforementioned properties on multithreading are not related to standards but are more hardware features (e.g., atomicity). And none of these properties are consistent with the semantics of volatile. For example, in order to achieve correct ordering, memory barriers would be required - something completely irrelevant with the notion of volatile. C\C++ standards until recently did not address multithreading, and each vendor implemented its own version (under Windows we can use WIN32 threads, under Linux we can use POSIX threads, under Solaris the pthread API implementation, etc.). So the correct way to implement multithreaded programs and avoid the undesirable side effects of race conditions would be to use a library or language extension that implements the atomic and memory fence semantics. An example of compliant code would be the same as the one offered in POS00-C. Avoid race conditions with multiple threads.

Compliant Solutions (For Example 1)

The compliant solutions for the previous noncompliant examples:

Compliant Solutions (For Example 2)

In the compliant solution the synchronization among threads is not through the Boolean done, but race conditions are avoided by acquiring mutexes.

Risk Assessment

Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

CON01-CPP

Medium

Probable

Medium

P8

L2

Other Languages

This rule can be found in the C Secure Coding Standard as CON02-C. Do not use volatile as a synchronization primitive.

Bibliography

C++ Standard ISO/IEC 14882
C++ standard ISO/IEC JTC 1/SC 22 N 4411
Article by Andrei Alexandrescu: Volatile - Multithreaded Programmer's Best Friend
David Butenhof: Programming with POSIX threads,  comp.programming.threads
The Volatile Keyword


7 Comments

  1. I added a comment inline and tried something with the title. Don't know if this is the right title or the right rule yet. It does read a bit like a book report. I would try to base your answer more in the N4411 as a definitive source.

  2. Good rule so far Efstathios. Comments:

    • While both C and C++ intend do address threading issues & concurrency in their forthcoming standards, I think it is legitimate to reference C99 and C++97 standards, as they are still widely used & adhered to.
    • Need a 'References' section, citing all the standards and articles referenced, including the Alexandrescu article.
    • 1st noncompliant code example should use "volatile bool flag" in the code, since that is what the subsequent text addresses.
    • The compliant solution has nothing to do with the noncompliant code examples. Provide compliant solutions for the two NCCE's.

    In general, compliant solutions that use pthreads should indicate that they are specific to POSIX. It would be nice if you can also provide compliant solutions for Windows, but we haven't always done that (sometimes we simply couldn't ):

    • Add a 'Risk Assessment' section.
    1. One other comment...this should be added as a recommendation, I think. Suggest labeling it MEM11-CPP

        • My comments above regarding the compliant/noncompliant code examples still applies.
        • Never cite a Wikipedia article as an academic source. It is non-authoritative, as anyone can edit your reference page and fill it with misinformation. Instead, consult the references cited by your article, and cite them as sources, if appropriate.
  3. There's a problem in tat C++2003 doesn't support pthreads, so this rule is technically a POSIX rule. But we have no POSIX section in C++ b/c it is no different than C.

  4. I'm moving this practice to the new Concurrency section.

    In the process, I noticed the Priority and Level didn't correspond to the Severity, Likelihood, and Remediation Cost values so I corrected the former.

  5. Regarding "Compliant Solution For Example 2". The results of code listings must be used elsewhere. Is it correct to assume that another thread reading buf contents (after obtaining mutex lock) will get updated (not stale) data, while we have buf non-volatile? I have a doubt, could you please clairify this?