Many C/C++ software engineers could go their entire life without ever needing to use the
volatile keyword. However in embedded systems, knowing how the
volatile keyword works and how to use it is a vital skill.
volatile keyword is a qualifier you apply to a variable, e.g.
volatile uint8_t my_var;. It tells the compiler that the value stored at this address may change at any time, even if the compiler cannot see a code path that would cause it to change. This forces the compiler to perform a read/write from memory every time the variable is used in the code, rather than the compiler storing the variable locally in a register or optimizing the read/write out completely!
You can place the keyword
volatile before or after the type when declaring a variable (
const behaves the same way). The basic syntax for defining a
volatile variable is:
In the embedded world, it is common to want to use pointers that point to volatile memory, i.e. a pointer to an 8-bit memory mapped register for controlling a UART. Pointers to volatile data are defined with:
volatile is used for:
- Variables which are read/written to from interrupts and also accessed outside the interrupt. If you forgot the
volatilethe compiler cannot set the code path that calls the interrupt (because interrupts are called asynchronously) and thus may assume nothing else touches the variable and cache the value in a register (or optimize out the read/write completely).
- Memory addresses which point to hardware registers in microcontrollers (memory mapped registers). These hardware registers are liable to be changed at any time by the hardware (e.g. a bit in a UART peripheral register which indicates when there are received bytes available for reading), so the compiler cannot assume it’s only changed when the code writes to it.
volatile should not be used for:
- Guaranteeing thread safe access of a shared variable between two threads.
volatileprovides some, but not all of the guarantees required for thread safe access. Other threading objects such as memory barriers, mutexes e.t.c are required, but these also provide the guarantees that
volatileis not needed in this use case.
The Use Of volatile With Buffers
It is also common to write incoming data to buffers (e.g. ring buffers) from interrupts in embedded firmware, and then read the data back out of the buffer outside of the interrupt in the main application routine (or flip the read/write for outgoing data).
volatile can be used when declaring the array of bytes in the buffer.
For example, to declare a 100-byte buffer which contains volatile data:
This forces the compiler to generate code in the main app routine that goes out and reads the values in the buffer. If you did not use
volatile, the compiler may infer that because the interrupt function that writes to the buffer never gets called (remember: the compiler can’t see the interrupt being called), so it never has to read the values from the buffer because it will always stay as the initialized values. This can result in you reading garbage data from your buffer!
Below is an example in C++ of a volatile buffer than is written to from an ISR (interrupt service routine) and read from a main application thread (outside of the interrupt). Because this is on a web-based system, I didn’t have access to true interrupts, so I simulated one with a separate thread instead. If you remove the
volatile keyword, you should get undefined behaviour. In the case when I ran it, I got the same output as when declared
volatile, which I think is because I couldn’t get the compiler to optimize out the read.
(run this code at https://replit.com/@gbmhunter/volatile-buffer-experiment)
memcpy() And volatile
memcpy() cannot operate on
volatile data. This can be an annoyance if you are trying to copy data to or from a buffer (array of bytes, i.e.
char *) which is declared
volatile. However, you can easily make your own version of
memcpy() which does work with
volatile. Below is a example of a
memcpy() equivalent that works with a destination buffer that is
Casting Away volatile
volatileon a variable can cause undefined behaviour.
In C++, you can use
const_cast to cast away
volatile (confusingly, it’s called
const_cast, but it actually casts away both
C++ Member Functions Declared volatile
TODO: Add info here.
This work is licensed under a Creative Commons Attribution 4.0 International License .