volatile keyword in C ( or volatile qualifier in C)

volatile qualifier in c

The volatile keyword is a qualifier it has a lot of importance in the programming language but problem is that many programmers are not aware of how to use the volatile keyword and when need to qualify a variable from the volatile keyword. Most of the textbooks don’t give importance to this topic either and hence it remains partially unexplained most of the time.

In this article, I will try to clear the concept of the volatile keyword and describe the benefits of the volatile qualifier in the C language. So let’s get started.

A volatile keyword is a qualifier that prevents the objects, from compiler optimization and tells the compiler that the value of the object can change at any time without any action being taken by the code. It prevents the cache from a variable into a register and ensures that every access variable is fetched from the memory.

According to the C standard, an object that has a volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects.

The volatile keyword is mainly used where we directly deal with GPIO, interrupt, or flag Register. It is also used where a global variable or buffer is shared between the threads.

 

Declaration of volatile keyword in C:

Like const, volatile is also a qualifier. So we only need to put the volatile keyword after or before the data type for the volatile variable at the time of variable declaration.

// Behavior of both variables should be same


int volatile data1;

volatile int data2;

 

Note: We can also use the volatile keyword with pointers.

Use of volatile keywords with pointers:

A volatile qualifier is a “C type qualifier” we can use with pointers. In the below section I am describing some situations where we can combine “volatile keyword” and pointer together. So let’s see the situation one by one.

1.)  Pointer to a volatile variable:

//piData is a pointer to volatile integer
int volatile *piData1;

volatile int *piData2;

 

Now both pointers become a pointer to a volatile integer variable. In this situation, the compiler assumes that the value of the variable, which is pointed by a pointer can change unexpectedly at any time. So whenever we access *piData, the compiler is always aware of unexpected changes in the variable value.

volatile keywords with pointers

//Volatile variable
volatile int iValue;

//pointer to volatile variable
volatile int* piData = &iValue;

 

Note: If an attempt is made to refer to an object defined with a volatile-qualified type through the use of an lvalue with a non-volatile-qualified type, the behavior is undefined.

If you declare a T-type variable as volatile, then you should not use T * to point this variable. If you will do, behavior is undefined. Let’s see below the example where T is an integer.

Wrong-way to access volatile variable using a pointer:

//volatile integer variable
volatile int iValue; 

//integer pointer
int* piData = &iValue;

 

Correct-way to access volatile variable using a pointer:

//Volatile variable
volatile int iValue;


//pointer to volatile variable
volatile int* piData = &iValue;

 

2.) Volatile pointer to the non-volatile variable:

Here pointer is volatile and pointing to a non-volatile object. It is rarely used.

//Non Volatile variable
int iValue;


//pointer to volatile variable
int* volatile piData = &iValue;

 

volatile keyword in C

3.) Volatile pointer to the volatile variable:

Here volatile pointer is pointing to a volatile object. Here optimization is not applicable to both pointer and variable both.

//Volatile variable
volatile int iValue;


//volatile pointer to volatile variable
volatile int * volatile piData = &iValue;



 

volatile qualifier in c

 

How to use volatile qualifier with structure?

We can use volatile keywords with user-defined data types like structure. Sometimes it is useful to use volatile keyword with user-defined data types.

If we used a volatile keyword at the time of declaration of a structure variable, then all members of the structure qualified with a volatile qualifier. But sometimes in the program, we need only some specific member as volatile so in that situation, we have to declare this member explicitly volatile.

It totally depends on requirements on how to use volatile qualifier with structure. Let’s see some examples where I have used volatile keyword with structure.

Example 1:

typedef struct
{
    unsigned int PortReg;
    unsigned int FlagReg;

} My_Reg;


//Use of volatile with structure variable
My_Reg volatile sMyHardwareReg;

In the above example, all member of the sMyHardwareReg is volatile.

 

Example 2.

typedef volatile struct
{
    unsigned int PortReg;

    unsigned int FlagReg;

} My_Reg;


My_Reg sMyHardwareReg;

Similar to example 1, sMyHardwareReg is also a variable of the user-defined data type. All members of sMyHardwareReg are volatile.

 

Example 3

typedef struct
{
    //volatile attribute
    unsigned int volatile PortReg;
    
    unsigned int FlagReg;
    
} My_Reg;

My_Reg sMyHardwareReg;

In the above example, PortReg is only a volatile member of the structure variable sMyHardwareReg.

 

struct variable as volatile vs marking individual fields volatile:

Let’s see a few examples to understand the difference when making struct variables as volatile and when making individual members volatile.

typedef struct
{
    int *piPortReg;

    int TimerReg;

} MyReg;

volatile MyReg sMyReg;

 

That would act like,

typedef struct
{
    int * volatile piPortReg;
    
    int volatile TimerReg;

} MyReg;

 

And not like,

typedef struct
{
    volatile int *piPortReg;
    
    int volatile TimerReg;

} MyReg;

 

So if a structure contains a pointer and you want to use this pointer to point volatile variable, then you have to implicitly use volatile with this pointer. Like the below expression,

typedef struct
{
    volatile int *piPortReg;
    
    int TimerReg;

} MyReg;

 

 

If you want to learn more about the C language, you can check this post which has some Free resources related to the C programming that might be helpful for you.

C Programming Courses And Tutorials

 

 

When need to use the volatile keyword?

It is very important to understand when to use the volatile keyword in the program. Many programmers know the concept of volatile but they are not aware of the situation where using a volatile keyword is beneficial. So here I am explaining situations where using a volatile keyword is beneficial.

1.)  The program works fine but when increase the optimization level of the compiler its behavior change and does not work as per the desire.

2.) Everything is going good but as soon as you enable the interrupt, code behavior changes and does not work as per the expectation.

3.) Flaky hardware drivers.

4.) Tasks that work fine in isolation but crash when another task is enabled.

How does volatile keyword affect the program?

The volatile keyword is used to restrain the compiler from making the assumption about the object value. It tells the compiler to re-read the value of the object in every execution.

For better understanding, I am taking a small program to describe the importance of volatile keyword.

// Hardware flag register
unsigned char FLAG_REG;

void fun (void)
{
    // Repeat while bit 0 is set
    while (FLAG_REG & 0x01)
    {
        //Perform any operation
    }
}

 

If we increase the compiler optimization level, then for better performance compiler load the FLAG_REG value in a register and does not re-read again although the value of FLAG_REG changed by the hardware. In that situation, your code would not be work as per your expectation.

But if you qualify the FLAG_REG from the volatile keyword, then the compiler understands that the value of FLAG_REG can change by the outer word so it avoids implementing any optimization on it.

// Hardware flag register
volatile unsigned char FLAG_REG;

void fun(void)
{
    // Repeat while bit 0 is set
    while (FLAG_REG & 0x01)
    {
        //Perform any operation
    }
}

 

What is the proper place to use a volatile qualifier?

A variable should declare volatile when its value can change unexpectedly. In practice, you must declare a variable as volatile whenever you are:

1.) Accessing the memory-mapped peripherals register.

2.) Accessing the global variables in an interrupt routine or signal handler.

3.) Sharing the global variables or buffers between the multiple threads.

Now Let’s see these three mentioned cases in detail where we must use a volatile qualifier.

Accessing  Peripheral Register

In the embedded system, all peripherals are located at a specific memory address. Peripheral has registers, the value of these registers can change asynchronously to code flow.

In a program, to access the peripherals register in a convenient way, we have to map the peripherals register with the C variable and access this variable using the pointer.

Note: In mapping not only care about the size and address of the registers but also need to care about its alignment in memory.

Consider a simple example, here a 32-bit flag status register at an address 0x40000000 and you have to monitor its first bit and wait in the loop until its first bit is one. Here if you will not use the volatile qualifier, then you will not get the proper result.

#define   STATUS_REG            (unsigned int*)(0x40000000)

volatile unsigned int  *const puiData = STATUS_REG;

// Wait until first bit is set
while((*puiData) & 0x01)
{
    
    //do some work
    
}

 

Accessing the global variables Interrupt service routine (ISR):

Often a global variable is shared between ISR and function. In the below example, a global variable (giFlag) is shared between ISR and the main() function. Let’s see an example code,

//Global flag
int giFlag = 0;

ISR(void)
{
    giFlag = 1;
}

int main(void)
{

    while (!giFlag)
    {
        //do some work
    }

    return 0;
}

In the above code, ISR is setting the value of the global flag and the main() function is monitoring the value of the global flag. The main() function does some other task until the global flag value is zero.

Everything will be might ok until you not turn on your compiler optimization. If you will turn on your compiler optimization, then might this code stop to work properly. Because it is unaware of the value changes by the ISR. So it assumes that while loop is always true and it never exits from the loop.

You can solve this problem by just using the volatile qualifier with the global flag. It prevents the compiler from applying any optimization on the global flag and tells the compiler that the value of this flag can change by the external event at any time without any action being taken by the code.

//Global volatile flag
volatile int giFlag = 0;

ISR(void)
{
    giFlag = 1;
}

int main(void)
{

    while (!giFlag)
    {
        //do some work
    }

    return 0;
}

 

Accessing the global variables between two are more threads (multi-thread application):

In a multithread application, two threads communicate to each other using the pipes or message queue but besides it, there is one more technique through which threads can communicate to each other this technique is shared location (shared buffer or global variable).

Generally, the thread is executed in an asynchronous way. If we do not declare these shared locations with the volatile keyword and we increase the optimization level of the compiler then the compiler will store these values in a local variable of thread context and always read the value from these local variables. So for the desired operation, we have to declare a shared buffer or global variable as volatile.

//Global flag
int gValue;

void Task_1(void) 
{
    gValue = 0; 
    
    while (gValue == 0) 
    {
        sleep(1);
    } 
    ...
}

void Task_2(void) 
{
    ...
    gValue++; 
    sleep(10); 
    ...
}

 

This code will likely fail once the compiler’s optimizer is enabled. We can resolve the problem by declaring gValue with the volatile qualifier.


const and volatile qualifier together:

The const volatile are fascinating keywords that make many people confused. It is very interesting to use volatile and const keywords together because the quality of the volatile (“any time-changing”) and const (“read-only”) seems like opposed, but sometimes it is useful to use this keyword together with a variable.

I have already written a detailed article ” const and volatile together” you can check this article.
In the below section, I am describing some scenarios where you can use volatile and const together.

Access the GPIO Register(Constant address):

One of the great use of volatile and const keyword together is at the time of accessing the GPIO registers. In the case of GPIO, its value will be changed by the ‘external factors’ (if a switch or any output device is attached with GPIO), if it is configured as an input. In that situation, volatile plays an important role and ensures that the compiler always read the value from the GPIO address and avoid making any assumption.

After using the volatile keyword, you will get the proper value whenever you are accessing the ports but still here is one more problem because the pointer is not const type so it might be your program change the pointing address of the pointer. So we have to create a constant pointer with a volatile keyword.

Let’s see an example,

unsigned int volatile * const pLcdReg = (unsigned int volatile *) 0x00020000;

In the above syntax, pLcdReg is a constant pointer to a volatile unsigned integer. The pLcdReg is pointing to a memory location 0x00020000 (GPIO Address).

volatile qualifier with constant

Using the pLcdReg, we can read from or write to value from the pointing GPIO address.

//Writing to memory location
*pLcdReg = WRITE_DATA; // to write data on LCD

//Reading from memory location
READ_DATA = *pLcdReg; //to read data from the LCD

 

Read-Only Shared-Memory Location:

If two processor communicates to each other using the shared memory location and the processor uses the location only to read the data, we have to make the location read-only type using the const keyword.

unsigned int const volatile gSharedFlag;

unsigned char const volatile acSharedBuffer[BUFFER_SIZE];

 

Read from the status register:

There are a lot of registers that are used to reflect the status of the device at different stages of the hardware. These registers are read-only types and their value changes asynchronously by the other events. If you want to access these registers, you have to use const and volatile together with pointers.

Example,

unsigned int const volatile * const pStatusFlagReg = (uint8_t *) 0x20000000;

//to read status from the status register
READ_DATA = * pStatusFlagReg;

// Not possible because address qualify by const keyword
*pStatusFlagReg = WRITE_DATA;

 

Your opinion matters:

Although here, I have tried to discuss a lot of points on volatile keywords and about their uses Ivwould like to know your opinion on the volatile keyword. So please don’t forget to write a comment in the comment box.

 

Recommended Post



55 comments

  1. such a nice explanation on volatile keyword with simple examples what make it easy to understand.

    thank you 🙂 keep it up.

  2. Very nice article .Good explanation for volatile keyword.Please add header files as an example of volatile declaration of any micro controller like PIC , LPC 2148 ,AVR etc.

  3. Very nicely explained Amlendra, after reading this post our lives will become volatile…
    Thanks Bro 🙂

  4. This is a good article for understanding the volatile qualifier. Despite your English grammar this is really good! +10
    I wish I could work on projects with you to learn more about embedded programming applications (I’m stil a student).

    Great Article Thank You!

  5. Very well explained with good examples.
    Please write an article on applications of union and where we use it.

  6. Very nice explanations Almendra with great examples.

    I have one doubt… Ca you please tell how “volatile” keyword works internally…??
    What happens when we put keyword “volatile”…??

  7. 1)Everything is going good but as soon as you enable the interrupt, code behavior change and does not work as per the expectation.
    2)Flaky hardware drivers.
    3)Tasks that work fine in isolation but crash when another task is enabled.

    can you please explain these points in detail?

  8. thanks bro it’s very clear and helpful i hope write articles for all qualifiers in depth like this

  9. Thanks for support bro….it very useful..and advanced concept explanation….keep going bro…upload lot like this…

  10. Nice articles tutorials thanks

    You missed some topics inline function,strings (strstr,strchr,fgets vs gets) ,it will be useful topics for implementations

Leave a Reply

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