volatile in C
This year I decided to learn C. In the beginning of my studies I came across the type qualifier volatile.
volatile int lock;
Writing volatile with the type in a variable says that the value may be examined or changed for reasons outside the control of the program at any moment. This is important to prevent the compiler to optimize away reads and writes in variable.
I wanted to find a way to simulate this behavior. To achieve this I created the following program:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
volatile int flag = 0;
void signal_handler(int signum) {
if (signum == SIGUSR1) {
printf("Signal received, setting flag.\n");
flag = 1;
}
}
int main() {
signal(SIGUSR1, signal_handler);
printf("Waiting for signal. Send with 'kill -SIGUSR1 %d'\n", getpid());
while (flag == 0) {
// Do nothing
}
printf("Flag changed, exiting.\n");
return 0;
}
This is a simple program which runs is a loop until the value of the variable flag is not 0 anymore.
This program also registers a signal handler for the signal SIGUSR1 using the signal()
function. In the signal_handler
function the flag value is changed to 1. Once the flag changes the the program should exit the while loop and print “Flag changed, exiting.”
Let’s test it. First we compile the program running:
$ cc -o volatile_test volatile_test.c
And the we run the program:
$ ./volatile_test
Waiting for signal. Send with 'kill -SIGUSR1 12679'
The program will enter an infinite loop, waiting for the signal. On other terminal we run
$ kill -SIGUSR1 12679
Back to the first terminal the program finishes after the signal was received.
Signal received, setting flag.
Flag changed, exiting.
Let’s now remove volatile
from the flag variable to see what happens.
int flag = 0;
We compile the program and and run it again.
$ cc -o volatile_test volatile_test.c
$ ./volatile_test
Waiting for signal. Send with 'kill -SIGUSR1 12822'
Again on another terminal we run the command to send the signal
$ kill -SIGUSR1 12822
Back to first terminal window, it finished the program, even in the version where we are not using volatile for the variable flag
.
To really be able to see the practical use of volatile we need to compile the program with optimizations.
To do this we use the -O
flag during compilation.
$ cc -O -o volatile_test volatile_test.c
If we repeat the test again running the program and sending the signal from other terminal we can see that the signal is received but the program continues to run.
$ ./volatile_test
Waiting for signal. Send with 'kill -SIGUSR1 13149'
Signal received, setting flag.
The optimizer sees that the variable flag never changes inside the while loop, therefore it assumes that the loop condition will always be true, and optimizes the loop to an infinite loop.
To correct the behavior, we add the volatile keyword back.
volatile int flag = 0;
And compile it again:
$ cc -O -o volatile_test volatile_test.c
Repeating the test we can see that now, even in the optimized code the program finishes correctly once the signal is received:
Signal received, setting flag.
Flag changed, exiting.
I ran this experiment on my MacBook with the Apple clang compiler, version 16.0.0. Keep in mind that results can differ based on the compiler and platform.
Hopefully, this example helps you understand what the type modifier volatile is used for.