Skip to content
Related Articles

Related Articles

Understanding “volatile” qualifier in C | Set 2 (Examples)

View Discussion
Improve Article
Save Article
  • Difficulty Level : Medium
  • Last Updated : 17 Jun, 2022
View Discussion
Improve Article
Save Article
 

The volatile keyword is intended to prevent the compiler from applying any optimizations on objects that can change in ways that cannot be determined by the compiler. 

Objects declared as volatile are omitted from optimization because their values can be changed by code outside the scope of current code at any time. The system always reads the current value of a volatile object from the memory location rather than keeping its value in a temporary register at the point it is requested, even if a previous instruction asked for the value from the same object. So the simple question is, how can the value of a variable change in such a way that the compiler cannot predict? Consider the following cases for an answer to this question: 

1) Global variables modified by an interrupt service routine outside the scope: For example, a global variable can represent a data port (usually a global pointer, referred to as memory mapped IO) which will be updated dynamically. The code reading the data port must be declared as volatile in order to fetch the latest data available at the port. Failing to declare the variable as volatile will result in the compiler optimizing the code in such a way that it will read the port only once and keep using the same value in a temporary register to speed up the program (speed optimization). In general, an ISR is used to update these data ports when there is an interrupt due to the availability of new data. 

 

2) Global variables within a multi-threaded application: There are multiple ways for threads’ communication, viz., message passing, shared memory, mail boxes, etc. A global variable is weak form of shared memory. When two threads are sharing information via global variables, those variables need to be qualified with volatile. Since threads run asynchronously, any update of global variables due to one thread should be fetched freshly by the other consumer thread. The compiler can read the global variables and place them in temporary variables of the current thread context. To nullify the effect of compiler optimizations, such global variables need to be qualified as volatile.

If we do not use volatile qualifier, the following problems may arise: 
1) Code may not work as expected when optimization is turned on. 
2) Code may not work as expected when interrupts are enabled and used.

Let us see an example to understand how compilers interpret volatile keyword. Consider the below code. We are changing the value of a const object using a pointer and we are compiling code without optimization option. Hence the compiler won’t do any optimization and will change the value of the const object.

C




/* Compile code without optimization option */
#include <stdio.h>
int main(void)
{
    const int local = 10;
    int *ptr = (int*) &local;
 
    printf("Initial value of local : %d \n", local);
 
    *ptr = 100;
 
    printf("Modified value of local: %d \n", local);
 
    return 0;
}


When we compile code with “–save-temps” option of gcc, it generates 3 output files:
1) preprocessed code (having .i extension) 
2) assembly code (having .s extension) and 
3) object code (having .o extension). 

We compiled code without optimization, that’s why the size of assembly code will be larger (which is highlighted in red below).

Output: 

  [narendra@ubuntu]$ gcc volatile.c -o volatile –save-temps
  [narendra@ubuntu]$ ./volatile
  Initial value of local : 10
  Modified value of local: 100
  [narendra@ubuntu]$ ls -l volatile.s
  -rw-r–r– 1 narendra narendra 731 2016-11-19 16:19 volatile.s
  [narendra@ubuntu]$

Let us compile the same code with optimization option (i.e. -O option). In the below code, “local” is declared as const (and non-volatile). The GCC compiler does optimization and ignores the instructions which try to change the value of the const object. Hence the value of the const object remains same. 

C




/* Compile code with optimization option */
#include <stdio.h>
 
int main(void)
{
    const int local = 10;
    int *ptr = (int*) &local;
 
    printf("Initial value of local : %d \n", local);
 
    *ptr = 100;
 
    printf("Modified value of local: %d \n", local);
 
    return 0;
}


For the above code, the compiler does optimization, that’s why the size of assembly code will reduce.

Output: 

  [narendra@ubuntu]$ gcc -O3 volatile.c -o volatile –save-temps
  [narendra@ubuntu]$ ./volatile
  Initial value of local : 10
  Modified value of local: 10
  [narendra@ubuntu]$ ls -l volatile.s
  -rw-r–r– 1 narendra narendra 626 2016-11-19 16:21 volatile.s

Let us declare the const object as volatile and compile code with optimization option. Although we compile code with optimization option, the value of the const object will change because the variable is declared as volatile which means, don’t do any optimization. 

C




/* Compile code with optimization option */
#include <stdio.h>
 
int main(void)
{
    const volatile int local = 10;
    int *ptr = (int*) &local;
 
    printf("Initial value of local : %d \n", local);
 
    *ptr = 100;
 
    printf("Modified value of local: %d \n", local);
 
    return 0;
}


Output: 

  [narendra@ubuntu]$ gcc -O3 volatile.c -o volatile –save-temp
  [narendra@ubuntu]$ ./volatile
  Initial value of local : 10
  Modified value of local: 100
  [narendra@ubuntu]$ ls -l volatile.s
  -rw-r–r– 1 narendra narendra 711 2016-11-19 16:22 volatile.s
  [narendra@ubuntu]$

The above example may not be a good practical example, but the purpose was to explain how compilers interpret volatile keyword. As a practical example, think of the touch sensor on mobile phones. The driver abstracting the touch sensor will read the location of touch and send it to higher level applications. The driver itself should not modify (const-ness) the read location, and make sure it reads the touch input every time fresh (volatile-ness). Such driver must read the touch sensor input in const volatile manner.

Note: The above codes are compiler specific and may not work on all compilers. The purpose of the examples is to make readers understand the concept.

Related Article : 
Understanding “volatile” qualifier in C | Set 1 (Introduction)
Refer to the following links for more details on volatile keyword: 
Volatile: A programmer’s best friend 
Do not use volatile as a synchronization primitive
This article is compiled by “Narendra Kangralkar“ and reviewed by GeeksforGeeks team. Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
 


My Personal Notes arrow_drop_up
Recommended Articles
Page :

Start Your Coding Journey Now!