There is a misconception that the volatile qualifier will provide the desired properties necessary for 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 the volatile
qualifier does not provide guarantees for any of these, neither by definition nor by the way it is implemented in various platforms. For more information on how volatile is implemented, consult DCL17-C. Beware of miscompiled volatile-qualified variables.
The C99 standard, section 5.1.2.3, paragraph 2, says:
Accessing a volatile object, modifying an object, modifying a file, 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 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.
This property of the volatile
keyword is sometimes confused as providing atomicity of a variable that is shared between threads in a multithreaded program. A variable declared as volatile
is not cached in a register leading to this confusion that it can be used safely as a synchronization primitive. When declared as volatile
the compiler does not re-order the sequence of reads and writes to that memory location. However, the compiler might re-order these reads and writes with those to other memory locations. This might result in non atomic operations on the synchronization variable resulting in errors.
Noncompliant Code Example
This noncompliant code example uses flag
as a synchronization primitive.
bool flag = false; void test() { while (!flag) { sleep(1000); } } void wakeup(){ flag = true; } void debit(unsigned int amount){ test(); account_balance -= amount; }
In this example, the value of flag
is used to determine if the critical section can be executed or not. Because the flag
variable is not declared as volatile
and so may be cached in registers. Before the value in the register is written to memory, another thread might be scheduled to run and so may end up reading stale data.
Noncompliant Code Example
This noncompliant code example uses flag
as a synchronization primitive but qualifies flag as a volatile
type.
volatile bool flag = false; void test() { while (!flag){ sleep(1000); } } void wakeup(){ flag = true; } void debit(unsigned int amount) { test(); account_balance -= amount; }
Declaring flag
as volatile solves the problem of reading stale data, but still does not provide atomicity guarantees needed for synchronization primitives to work correctly. The volatile
keyword does not promise to provide the guarantees needed for synchronization primitives.
Compliant Solution
This code uses a mutex to protect critical sections.
#include <pthread.h> int account_balance; pthread_mutex_t flag = PTHREAD_MUTEX_INITIALIZER; void debit(unsigned int amount) { pthread_mutex_lock(&flag); account_balance -= amount; /* Inside critical section */ pthread_mutex_unlock(&flag); }
Risk Assessment
Recommendation |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
POS03-C |
Medium |
Probable |
Medium |
P12 |
L1 |
Other Languages
This rule can be found in the C++ Secure Coding Practices as CON01-CPP. Do not use volatile as a synchronization primitive
Bibliography
[ISO/IEC 9899:1999] Section 5.1.2.3 "Program Execution"
[Open Group 04] Section 4.11 "Memory Synchronization"