Use of the C keyword volatile is critical to many embedded software applications, but it is generally not well understood. In addition, a number of compilers do not implement the keyword's functionality correctly, which compounds the problem. This article provides some guidelines for the use of volatile variables .
Modern compilers perform such a wide range of optimizations that defining their function as 'translating C into assembly language' no longer holds. It is better to think in terms of 'translating an algorithm expressed in C into a functionally identical one expressed in assembly language.' To differentiate between these definitions may seem like nit-picking, but it's necessary because the progress in compiler optimization technology has significant implications for developers.
When debugging fully optimized code, a problem can occur when an unintended change to the functionality of the code results from optimization. This can happen if there is a fault in the complier, of course, but it can also happen when the compiler has insufficient information about the system to make the best decisions. Although this situation is not unique to embedded applications, it is much more likely because, unlike desktop computers, every embedded system is different.
Variables, memory and registers
The allocation of a variable in C is more complex than might be expected. For the purposes of this article we will consider only simple types like int .
Variables may be defined outside of functions. Such variables are termed 'external' and are allocated a specific memory location. The actual allocation of an address is most likely done by the linker, but the compiler determines that it should be fixed. External variables can also be qualified as static, which seems redundant as they will always be allocated to static memory. However, in this case the qualification simply limits the scope of the variable to the (rest of) the current module (source file).
Variables defined inside functions are normally 'automatic' and may be explicitly declared by applying the (rarely used) keyword auto . An automatic variable is allocated storage space in dynamic memory, which is local to the function. Traditionally, this was on the stack within a frame created at the start of the function and discarded on exit.
It is also possible to define variables internal to a function, that are allocated static storage. This results in the value of the variable being preserved after exit from the function, even though it is out of scope. This has two uses: the value will remain so that it will be available for a future call to the same function, and the function can place results in such variables and return a pointer safely. Such a variable is defined using the keyword static .
Optimized variable access
Modern compilers automatically allocate a register to heavily used automatic variables for the duration of their lifetime (which may be shorter than the time in which they are technically in scope). The programmer can give a hint to the compiler that such register allocation may be helpful by using the keyword register. This has no negative effect on the operation of the software – just an improvement in execution speed.
Repeated access to statically stored variables (and to automatics that are allocated space on the stack) may be optimized by caching its value temporarily in a register. This might apply one of three scenarios:
- The variable is read once and its value retained for future uses.
- The variable is only written when the last opportunity to do so arises.
- (1) and (2) may be combined.
Again, this optimization normally has no negative effect on the operation of the software – just an improvement in execution speed.
Most of the time, variable caching optimization is great, as it provides a measurable improvement in code execution performance. However, there are two circumstances, not uncommon in embedded software, where a problem may occur with external variables:
a. The variable is shared with other execution threads – tasks or interrupt service routines.
b. The variable is mapped on to a hardware machine register.
n both cases, the value may change at any time, so using a cached copy is inappropriate. Consider this code:
The loop is likely to continue forever, as the initial value (0) of the variable flag_from_ISR will most likely be cached.
In the case of (b), multiple writes may not be performed because of the same optimization. For example, in this case:
machine_register = 0x01;
machine_register = 0x02;
machine_register = 0x04;
only the final write to machine_register is likely to actually occur, as the compiler would assume that the first two are redundant.
Although most compilers would have an option to switch off this particular optimization, that would be overkill for just a few variables that are causing a problem. The keyword volatile is available to switch off the optimization for specific variables.
Using the keyword 'volatile'
The keyword volatile is appliedas a qualifier to the declaration or definition of variables that needto be protected from caching. Thus, the declarations from the aboveexamples would be written:
volatile int flag_from_ISR=0;
extern volatile int machine_register;
Forthe most part, using volatile is straightforward, like any otherqualifier in C. The only potential problem is the need for care withsyntax that has pointers – is the pointer itself volatile or the objectto which it is pointing? Here is the correct syntax:
volatile int* pi1=0; // pointer to volatile int
int* volatile pi2=0; // volatile pointer to int
volatile int* volatile pi3=0; // volatile pointer to volatile int
Incidentally, this syntax is the same for the const keyword.
ModernC and C++ compilers implement volatile, albeit with varying degrees ofquality and accuracy. The keyword was introduced with ANSI C; it was nota keyword in K&R C and it was not implemented in early versions ofthe C++ standard, but it's now universally available.
Threads and 'volatile'
Althougha variable that is shared between execution threads should be declaredvolatile, this may not be a sufficient precaution, but there are anumber of texts that suggest that volatile is enough. To illustrate theproblem, consider this code:
volatile x = 1;
Syntacticallythis is fine, but what about the generated code? If the CPU instructionset allows a memory location to be incremented directly with a single,non-interruptible instruction, there is no problem(so long as thecompiler uses those instructions. But many devices will require the datato be read, incremented,and written back. This is all fine according tothe language definition, but what if an interrupt occurs during thissequence of instructions?
You need to protect access to it sothat the memory location always contains a valid value. You could dothis by disabling and re-enabling interrupts or, with an RTOS, asemaphore might be the answer.
The keywordvolatile solves a problem experienced by many embedded softwaredevelopers, but it is necessary to methodically identify which variablesneed to be declared volatile. The chosen compiler’s implementation ofthe keyword needs to be carefully verified. In a multi-threadingcontext, the use of volatile needs particular care, as it may notprovide the expected protection.
Colin Walls has overthirty years experience in the electronics industry, largely dedicatedto embedded software. A frequent presenter at conferences and seminarsand author of numerous technical articles and two books on embeddedsoftware, Colin is an embedded software technologist with MentorEmbedded (the Mentor Graphics Embedded Software Division), and is basedin the UK. His regular blog is located at: blogs.mentor.com/colinwalls . His email is email@example.com. You can also follow him on Google+.