Introduction
"Volatile" is a qualifier in 'C' which is applied to a variable when it is declared. So, what does it tells to the compiler? It gives the information to the compiler that the value of the variable may change at any time during the execution of the code without the knowledge of the compiler. If proper precautions are not taken, we might not get the desired results. A variable should be declared volatile whenever its value could change unexpectedly.
The syntax for declaring the variable as volatile is given below,
volatile
dataTpye variable;
Let us understand the “Volatile” keyword in
deep through the following examples.
Example 1:
Let
us consider small and simple example as shown in the Fig:1 to study
the behavior of the 'volatile' keyword in C.
Fig
1: Code without the use of volatile.
|
In
the above example the intention of the programmer is to keep polling
inside the while loop until 'flag' value becomes 1(one).But the
compiler, while compiling the code applies the optimization
techniques and compiler will notice that no other code can possibly
change the value stored in 'flag', and therefore assume that it will
remain equal to 0(Zero) all times. The compiler will then replace the
function body with an infinite loop as shown in the below Fig 2
Fig
2: Optimization applied by the compiler to the code shown in fig1.
|
Let
we check the size of the assembly code generated by the compiler as
shown in the below Fig 3.
Fig
3: Size of assembly code generated by the compiler.
|
Now,
if you observe Fig 3, the size can be found as 482 bytes in the 5th
column. Now, we will apply the volatile keyword to the
flag variable to the code shown in Fig 1, as shown in the below Fig 4,
Fig 4: Code with
volatile
|
Let
we check the size of the assembly code generated by the compiler as
shown in the below Fig 5.
Fig5:
Size of assembly code generated by the compiler after applying
'volatile' keyword.
|
Now,
if you observe Fig 5, the size can be found as 501 bytes in the 5th
column. So, when we compare the sizes of both the codes with &
without volatile keyword, obviously one can observe that the compiler
is not optimizing the variable flag when it is qualified as
“Volatile”.
Let we still experiment further to explore where
the compiler is optimizing the code, to do this apply the vimdiff
command to the assembly codes generated earlier, the difference is
shown in the below fig 6:
Fig 6: Difference between the assemblies codes
generated without & with volatile keyword.
|
From the above figure, we can conclude that
volatile keyword prevents the application of optimization techniques
by the compiler.
Example 2
Let us consider another example, where “for”
loops are used commonly in the Embedded C code for the generation of
the delays. Let us see how the compiler will optimize the code
containing the “for” loops in the embedded C code without the use
of the qualifier “Volatile” as shown in the Fig 7 below,
Fig7: For loop without volatile qualifier.
|
Let us generate the assembly code for the above
given example, using the command given in the note 2, and getting the
size of the assembly code using the “ls” command is given below
in Fig 8,
Fig8: Size
of assembly code generated by the compiler without
volatile qualifier.
|
Now, we will apply the volatile keyword to the “i”
variable in the code shown in Fig 7, as shown in the below Fig 9,
Fig9: Code with volatile keyword.
|
Let us generate the assembly code for the above
given example, using the command given in the note 2, and getting the
size of the assembly code using the “ls” command is given below
in Fig 10,
Fig10: Size
of assembly code generated by the compiler with
volatile qualifier.
|
Comparing the sizes in the Fig 8 & 10, one can
identify the compiler is applying the optimization techniques without
the volatile qualifier. The dis-assembly code for both with &
without volatile keyword is shown below in Fig 11,
Fig 11: Difference between the assemblies codes
generated without & with volatile keyword.
|
Example 3 : Global variables accessed
by multiple tasks within a multi-threaded application
Let
us consider one more example to show how the global variable will be
affected by the compiler optimization in the multi-threaded
application. The example code snippet is shown as below in Fig 12,
Fig
12: Demo code to show how global variable will be affected in
multi threaded program.
|
In
the above demo program, the compiler doesn't have any knowledge of
context switching between the two threads. If the compiler
optimizations are turned “ON” then the compiler will assume that
global_item_count variable is always “ZERO” and no other part of
the thread is attempting to modify it. So, the compiler may replace
the line no. 11 in the demo code like this
Which
is nothing but the infinite loop, so in-order to avoid such
optimizations by the compiler, it is safe to declare the variable
global_item_count as “volatile”.
Similarly,
one can realize the effect of producer-consumer problem accessing the
global variable without declaring it as “Volatile”. Refer the
link below
Example 4: Interrupt
service routines
Let
us consider another example given
in the fig 13, where
“volatile” plays a very important role in the ISR.
Fig 13: Volatile keyword used in ISRs
|
In
the above example, if the flag is not declared as “Volatile” ,
then the compiler may optimize the code assuming always the flag is
ZERO and replace the while(!flag) to while(TRUE) in
line no.11, which is nothing but infinite loop. But the
flag value will change when the interrupt occurs.
Whether to declare the variable as 'Volatile' or
not is cross compiler dependent, anyhow it is good practice to
declare the variable as 'Volatile' to achieve the portability of the
code.
Conclusion:
The main use of volatile keyword is to prevent
compiler from optimizing the code in terms of time complexity by
generating a code that uses CPU registers as faster ways to represent
variables. By declaring the variable as “Volatile” forces
compiled code to access the exact memory location in RAM on every
access to the variable to get the latest value of it which may have
been changed by another entity.
A variable should be declared volatile whenever
its value could change unexpectedly. In real time, three types of
variables could change,
1. Memory-mapped peripheral registers
2. Global variables modified by an interrupt
service routine
3. Global variables accessed by multiple tasks
within a multi-threaded application