...
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <signal.h>
sig_atomic_t interrupted; /* bug: not declared volatile */
void sigint_handler(int signum) {
interrupted = 1; /* assignment may not be visible in main() */
}
int main(void) {
signal(SIGINT, sigint_handler);
while (!interrupted) { /* loop may never terminate */
/* do something */
}
return 0;
}
|
Compliant Solution
By adding the volatile qualifier to the variable declaration, interrupted 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 <signal.h>
volatile sig_atomic_t interrupted;
void sigint_handler(int signum) {
interrupted = 1;
}
int main(void) {
signal(SIGINT, sigint_handler);
while (!interrupted) {
/* do something */
}
return 0;
}
|
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, though it provides some guarantees. Integer values ranging from SIG_ATOMIC_MIN through SIG_ATOMIC_MAX may be safely stored to a variable of the type. In addition, when sig_atomic_t is a signed integer type, SIG_ATOMIC_MIN must be no greater than -127 and SIG_ATOMIC_MAX no less than 127. Otherwise, SIG_ATOMIC_MIN must be 0 and SIG_ATOMIC_MAX must be no less than 255. The macros SIG_ATOMIC_MIN and SIG_ATOMIC_MAX are defined in the header <stdint.h>.
Noncompliant Code Example (Cast to volatile)
In this noncompliant code example, the thread_func function 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, because end_processing is not declared volatile, the assignment to it in the body of the loop does not need to be flushed from a register into memory and consequently 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 data races 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.
| Code Block | ||||
|---|---|---|---|---|
| ||||
extern int compute(void*);
static _Bool end_processing;
void* thread_func(void *arg) {
while (0 == *(volatile _Bool*)&end_processing) {
int status;
status = compute(arg);
if (status) {
/* notify other threads to end processing */
end_processing = 1;
break;
}
}
return 0;
}
|
Compliant Solution
By adding the volatile qualifier to the variable declaration, interrupted end_processing is guaranteed to be accessed read from and written to its original address for every iteration of the while loop as well as from within the signal handler.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <signal.h> volatile sig_atomic_t interruptedextern int compute(void*); static volatile _Bool end_processing; void* sigintthread_handlerfunc(intvoid signum*arg) { interruptedwhile (0 == 1; } int main(void) { signal(SIGINT, sigint_handler); while (!interrupted) { /* do something */ } end_processing) { int status; status = compute(arg); if (status) { /* notify other threads to end processing */ end_processing = 1; break; } } return 0; } |
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, though it provides some guarantees. Integer values ranging from SIG_ATOMIC_MIN through SIG_ATOMIC_MAX may be safely stored to a variable of the type. In addition, when sig_atomic_t is a signed integer type, SIG_ATOMIC_MIN must be no greater than -127 and SIG_ATOMIC_MAX no less than 127. Otherwise, SIG_ATOMIC_MIN must be 0 and SIG_ATOMIC_MAX must be no less than 255. The macros SIG_ATOMIC_MIN and SIG_ATOMIC_MAX are defined in the header <stdint.h>Note, however, that declaring an object volatile is not sufficient to prevent data races 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. See CON02-C. Do not use volatile as a synchronization primitive for more information.
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.
...