C volatile keywords

Home / C volatile keywords

C volatile keywords

October 19, 2015 | Article | No Comments

In C/C++ programming language, volatile keyword is a type qualifier used to declare an object that can be modified in the program by something such as operating system, the hardware, or a concurrently executing thread.

In reality, the C’s volatile keyword is poorly understood by many programmers and used not properly. It’s not surprising as most book covering C language only explain it in one or two sentence. This article will covering about a proper way to use volatile. This article is categorized as little advanced so I hope you have understand C\C++.

Mostly, people experience these while programming C\C++ for embedded code (check for yourself):

  • Code works fine until compiler optimizations enabled
  • Code works fine until interrupts enabled
  • Flaky hardware drivers
  • RTOS tasks that work fine in isolation until other task is spawned.

If you ever experience any of above, most likely you didn’t use the C keyword volatile.

C’s volatile keyword is a qualifier that is applied to a variable when declared and tells the compiler that the value of the variable may change at any time without any action being taken by the code that compiler finds nearby. This is quite serious.

There are some declaration that may have different meaning. Let’s inspect further.

To declare a variable volatile, include the keyword volatile before or after the data type in variable declaration. Both have same meaning:

volatile int xathrya;
int volatile xathrya;

Those are very common declaration. Now, there is another way of declaring pointer. Both of these declaration will declare a pointer to a volatile unsigned integer:

volatile unsigned int * xathrya;
unsigned int * xathrya;

Next, we can have volatile pointers to non-volatile data. But this is very uncommon:

int* volatile xathrya;

You may think that we can have a volatile pointer to volatile variable, and yes this is possible:

int volatile * volatile xathrya;

If you apply volatile to a struct, union, or a class, the entire contents of struct/union/class would be volatile too. If you don’t want this, apply volatile to individual members of struct/union/class.

Now speaking about proper use of volatile. When we declare volatile? Whenever a variable’s value could change unexpectedly. In practice there are only three types of variable could changes:

  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

Let’s discuss each case!

Peripheral registers

Embedded systems contain real hardware, usually with sophisticated peripherals. These peripherals contain registers whose values may change asynchronously to the program flow. As a very simple example, consider an 8-bit status register that is memory mapped at address 0x1234. It is required that you poll the status register until it becomes non-zero. The naive and incorrect implementation is as follows:

uint8_t* pReg = (uint8t *) 0x1234;
// wait for register to become non-zero

while(*pReg == 0) {  } // do something else

This will almost certainly fail as soon as you turn compiler optimization on, since the compiler will generate assembly language that looks something like this:

mov ptr, 0x1234
mov a, ptr
loop:
bz loop

The rationale of the optimizer is quite simple: having already read the variable’s value into the accumulator (on the second line of assembly), there is no need to reread it, since the value will always be the same. Thus, in the third line, we end up with an infinite loop. To force the compiler to do what we want, we modify the declaration to:

uint8_t volatile* pReg = (uint8_t volatile *) 0x1234;

Thus the assembly language would looks like:

mov ptr, 0x1234
loop:
mov a, ptr
bz loop

The desired behavior is achieved.

Subtler problems tend to arise with registers that have special properties. For instance, a lot of peripherals contain registers that are cleared simply by reading them. Extra (or fewer) reads than you are intending can cause quite unexpected results in these cases.

Interrupt Service Routines

Interrupt service routines often set variables that are tested in mainline code. For example, a serial port interrupt may test each received character to see if it is an ETX character (presumably signifying the end of a message). If the character is an ETX, the ISR might set a global flag. An incorrect implementation of this might be:

int etx_rcvd = FALSE;

void main() {
...

   while(!ext_rcvd) {

      // wait
   }

...

}

interrupt void rx_isr(void) {

...

   if(ETX == rx_char) {
      etx_rcvd = TRUE;
   }

...

}

With compiler optimization turned off, this code might work. However, any half decent optimizer will “break” the code. The problem is that the compiler has no idea that etx_rcvd can be changed within an ISR. As far as the compiler is concerned, the expression !ext_rcvd is always true, and, therefore, you can never exit the while loop. Consequently, all the code after the while loop may simply be removed by the optimizer. If you are lucky, your compiler will warn you about this. If you are unlucky (or you haven’t yet learned to take compiler warnings seriously), your code will fail miserably. Naturally, the blame will be placed on a “lousy optimizer.”.

The solution is to declare the variable etx_rcvd to be volatile. Then all of your problems (well, some of them anyway) will disappear.

Multi-threaded applications

Despite the presence of queues, pipes, and other scheduler-aware communications mechanisms in real-time operating systems, it is still fairly common for two tasks to exchange information via a shared memory location (that is, a global). Even as you add a preemptive scheduler to your code, your compiler has no idea what a context switch is or when one might occur. Thus, another task modifying a shared global is conceptually identical to the problem of interrupt service routines discussed previously. So all shared global variables should be declared volatile. For example, this is asking for trouble:

int cntr;

void task1(void) {
   cntr = 0;
   while(cntr == 0) {
      sleep(1);
   }

...

}

void task2(void) {

...

   cntr++;
   sleep(10);

...

}

With a simple observation, we will know that this code will likely fail once the compiler’s optimizer is enabled. Declaring cntr to be volatile is the proper way to solve it.

Some compilers allow you to implicitly declare all variable as volatile. But, resist this temptation since it is essentially a substitute for thought. It also leads to potentially less efficient code. Use volatile if you must.

,

About Author

about author

xathrya

A man who is obsessed to low level technology.

Leave a Reply

Your email address will not be published. Required fields are marked *

Social media & sharing icons powered by UltimatelySocial