Understanding the keyword "volatile" in C (and C++) language
The keyword volatile
in C language (and also C++) is often misunderstood and underrated. However, it plays a crucial role in certain contexts, particularly in the field of embedded systems.
In this article, we'll explore the uses cases of the volatile
keyword, see why it is required and how to properly use it.
What is the volatile
keyword ?
Before seeing how to use it, let's first see what the volatile
keyword is.
This is a type qualifier, just like the keyword const
for example. It is used to inform the compiler that the value of a variable might change unpredictibly, i.e. due to an action other than the code being executed. By indicating that to the compiler, we inform it that it shall not do any assumptions about the value of this variable and that it shall always read its value before using it.
Why use the keyword volatile
?
Modern compilers bring numerous optimizations to improve the execution performance of applications. One of such optimizations is to actually read only once the value of a variable, even if it's used multiple times in a function. This optimization allows to save accesses to the memory bus, which might be expensive on systems with limited resources.
While this is a good thing and works just fine if the variable is indeed modified only by the function being executed, this can have undesired effects if the variable may also be changed in another context (see cases described below). In such situations, the function may miss state changes of the variable.
Shared memory among multiple threads
In multithreaded applications (sometimes also called multi-tasks applications), some variable may be accessed by more than one thread. This is often used as a notification mechanism between two threads.
For instance, a thread A will constantly monitor a flag, while a thread B will modify this flag in some cases to notify thread A of a specific situation.
Shared memory between the application and an interrupt service routine (ISR)
In a similar way as described above, some memory areas may be shared between the application and an interrupt service routine. This is very frequent in embedded systems, so that an ISR notifies the application of a given event.
For example, if a GPIO input is changed from 0 to 1, the corresponding interrupt service routine will set a flag at 1. This flag is constantly monitored by the application in its main loop. When it detects the flag is 1, it peforms a predefined action.
Hardware registers
In embedded systems, it is very common that the application monitors the state of a peripheral register, in particular when that peripheral is used in "polling" mode. In this case as well, the the read variable may change at any time in an unpredictable manner, as it does not depend on the code, but on an external event. In this case as well, the keyword volatile
will be used.
Example of use
In the basic example below, let's admit we want to execute a given action if a given type of interrupt occurs, thus calling interrupt_handler()
. In this interrupt service routine, flag
will be set to 1 (the 'U' suffix indicates the litteral constant is unsigned).
In parallel to that, the main loop of the application will constantly monitor the state of flag
. If flag
is 1, then the action represented by the comment "Do something" will be performed, and flag
will be reset.
volatile uint8_t flag = 0U; // Shared variable
void interrupt_handler()
{
flag = 1U; // Notify the application
}
int main()
{
while (1) { // Main loop
if (flag == 1U) {
// Do something
Flag = 0U; // Once used, clear flag
}
}
}
Conclusion
In this article, we described what the keyword volatile
is, and in which cases it shall be used.
Omitting volatile
when it is required will introduce bugs which may be difficult to find, in particular in complex applications. Thus, it is crucial to have the righe reflexes to use it in the use cases described in this article.