Combining C's volatile and const keywords

February 24, 2012

Michael.Barr-February 24, 2012

Does it ever make sense to declare a variable in C or C++ as both volatile (in other words, "ever-changing") and const ("read-only")? If so, why? And how should you combine volatile and const properly?

One of the most consistently popular articles on the Netrino website is about C's volatile keyword. The volatile keyword, like const, is a type qualifier. These keywords can be used by themselves, as they often are, or together in variable declarations.

I've written about volatile and const individually before. If you haven't previously used the volatile keyword, I recommend you read "How to Use C's volatile Keyword" on before going on. As that article makes plain:

"C's volatile keyword is a qualifier that is applied to a variable when it is declared. It tells the compiler that the value of the variable may change at any time--without any action being taken by the code the compiler finds nearby."

How to use C's volatile keyword
By declaring a variable volatile, you're effectively asking the compiler to be as inefficient as possible when it comes to reading or writing that variable. Specifically, the compiler should generate object code to perform each and every read from a volatile variable as well as each and every write to a volatile variable--even if you write it twice in a row or read it and ignore the result. Not a single read or write can be skipped. In other words, no compiler optimizations are allowed with respect to volatile variables.

The use of volatile variables may also create additional sequence points within the functions that access them. In a nutshell, the order of accesses of volatile variables A and B in the object code must be the same as the order of those accesses in the source code. The compiler is not allowed to reorder volatile variable accesses for any reason. (Consider what might go wrong if the referenced memory locations were hardware registers.)

Here are two examples of declarations of volatile variables:
int volatile g_shared_with_isr;
uint8_t volatile * p_led_reg = (uint8_t *) 0x80000;
The first example declares a global flag that can be shared between an ISR and some other part of the code (such as a background processing loop in main() or a task on top of an RTOS) without fear that the compiler will optimize (in other words, "delete") the code you write to check for asynchronous changes to the flag's value. It's important to use volatile to declare all variables that are shared by asynchronous software entities, which is important in any kind of multithreaded programming. (Remember, though, that access to global variables shared by tasks or with an ISR must always also be protected via a mutex or interrupt disable, respectively.)

The second example declares a pointer to a hardware register at a known physical memory address (80000h)--in this case to manipulate the state of one or more LEDs. Because the pointer to the hardware register is declared volatile, the compiler must always perform each individual write. Even if you write C code to turn an LED on followed immediately by code to turn the same LED off, you can trust that the hardware really will receive both instructions. Because of the sequence point restrictions, you are also guaranteed that the LED will be off after both lines of the C code have been executed. The volatile keyword should always be used with creating pointers to memory-mapped I/O such as this.

[See my Coding Standard Rule #4: Use volatile whenever possible for best practice recommendations on the use of volatile by itself.]

< Previous
Page 1 of 3
Next >

Loading comments...