
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.
Wiki Markup |
---|
The {{volatile}} keyword eliminates this confusion by imposing restrictions on access and caching. According to the C99 Rationale \[[ISO/IEC 03|AA. C References#ISO/IEC 03]\]: |
. 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 compilation to permit race conditions.
The volatile
keyword eliminates this confusion by imposing restrictions on access and caching. According to the C99 Rationale [C99 Rationale 2003],
No caching No cacheing through this lvalue: each operation in the abstract semantics must be performed (that is, no cacheing caching 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 that a compiler does not perform unintended reordering or optimization, it in no way guarantees Type qualifying objects as volatile does not guarantee synchronization between multiple threads or otherwise ward , protect against simultaneous memory accesses, or, 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 shared objects in signal handlers.
Non-Compliant Coding Example
If the value of i
is cached, the while
loop may never terminate. When compiled on gcc
with the -O
optimization flag, the program fails to terminate even upon receiving a SIGINT
.
Code Block | ||
---|---|---|
| ||
#include <signal.h>
sig_atomic_t i;
void handler(int signum) {
i = 0;
}
int main(void) {
i = 1;
signal(SIGINT, handler);
while (i) {
/* do something */
}
return 0;
}
|
Non-Compliant Coding Example
The following code prevents the compiler from optimizing away the loop condition, by typecasting the variable to volatile
within the while loop.
However, type qualifying objects as volatile does ensure that a conforming compiler will not elide or reorder access to the object.
Noncompliant Code Example
In this noncompliant code example, the programmer is targeting a custom piece of hardware that controls an LED by writing values into a register bank. The register bank is memory mapped into the process such that writing to a specific memory location will actually place a value into a hardware register to be read by the LED controller. The programmer intends to turn the LED on by placing value 1 into the first register, and then turn the LED off later by placing the value 0 into the first register.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stddef.h>
#include <stdint.h>
extern void get_register_bank(int32_t **bank,
size_t *num_registers);
extern void external_wait(void);
void func(void) {
int32_t bank[3];
size_t num_regs = 3;
get_register_bank((int32_t **)&bank, &num_regs);
if (num_regs < 3 | ||||
Code Block | ||||
| ||||
#include <signal.h> sig_atomic_t i; void handler(int signum) { i = 0; } int main(void) { i = 1; signal(SIGINT, handler); while (*(volatile sig_atomic_t *)&i) { /* doHandle somethingerror */ } bank[0] return= 1; external_wait(); bank[0] = 0; } |
Such a solution may be necessary to prevent the compiler from optimizing away the memory lookup while allowing for caching and optimizations elsewhere in the code.
However, this solution violates SIG31-C. Do not access or modify shared objects in signal handlers.
Compliant Solution
The compiler is free to optimize what it perceives as being a dead store to bank[0]
by removing the first assignment to the variable. This would cause the LED to never be turned on in an optimized build.
Compliant Solution
In this compliant solution, the register bank's memory is qualified with the volatile
keyword, ensuring the compiler does not optimize access to the memoryBy adding the volatile
qualifier to the variable declaration, i
is guaranteed to be accessed from its original address for every iteration of the while
loop, as well as from within the signal handler.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stddef.h> #include <signal<stdint.h> extern void get_register_bank(volatile sig_atomicint32_t i; void handler(int signum) { i = 0; } int main(void) { i = 1; signal(SIGINT, handler); while (i) { /* do something */ } return 0; } |
Wiki Markup |
---|
The {{sig_atomic_t}} type is the integer type of an object that can be accessed as an atomic entity, even in the presence of asynchronous interrupts. The type of {{sig_atomic_t}} is [implementation-defined|BB. Definitions#implementation-defined behavior], although there are constraints. Only assign integer values from 0 through 127 to a variable of type {{sig_atomic_t}} to be fully portable (see \[[SIG31-C. Do not access or modify shared objects in signal handlers]\]). |
Risk Assessment
Failing to use the volatile
qualifier can result in race conditions in asynchronous portions of the code, causing unexpected values to be stored and leading to possible data integrity violations.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
DCL34-C | 2 (medium) | 1 (unlikely) | 2 (medium) | P4 | L3 |
**bank,
size_t *num_registers);
extern void external_wait(void);
void func(void) {
volatile int32_t bank[3];
size_t num_regs = 3;
get_register_bank((volatile int32_t **)&bank, &num_regs);
if (num_regs < 3) {
/* Handle error */
}
bank[0] = 1;
external_wait();
bank[0] = 0;
} |
Risk Assessment
Failure to declare variables containing data that cannot be cached as volatile can result in unexpected runtime behavior resulting from compiler optimizations.
Recommendation | Severity | Likelihood | Detectable | Repairable | Priority | Level |
---|---|---|---|---|---|---|
DCL22-C | Low | Probable | No | Yes | P4 | L3 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
LDRA tool suite |
| 8 D | Partially implemented | ||||||
Parasoft C/C++test |
| CERT_C-DCL22-a | Avoid unused values | ||||||
Polyspace Bug Finder |
| Checks for write without a further read (rule partially covered) |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
...
Related Guidelines
Bibliography
[C99 Rationale 2003] | Subclause 6.7.3, "Type Qualifiers" |
...
"Type qualifiers" \[[Sun 05|AA. C References#Sun 05]\] [Chapter 6, "Transitioning to ISO C"|http://docs.sun.com/source/819-3688/tguide.html#pgfId-997898]DCL33-C. Ensure that restrict-qualified source and destination pointers in function arguments do not reference overlapping objects 02. Declarations and Initialization (DCL) DCL35-C. Do not convert a function pointer to an incompatible type