Spilling the Beans: volatile Qualifier

It is interesting to see that some aspects (mostly unintended) can stimulate lots of good and fruitful discussions. So this happened with “Spilling the Beans: Endless Loops” (recommended to read πŸ™‚ where using (or not using) volatile for inline assembly created thoughts which warrant an article on that subject.

The volatile qualifier in C/C++ is misunderstood by many programmers, or wrongly used.

Photo by Tara Winstead on Pexels.com

Still, ‘volatile’ is very useful if you know what it means for the compiler and what is good use of it.

Code like the one below is simply fully wrong, only trying to hack around the real problem (lack of re-entrancy). So if you see something like this, you better don’t use that code, because that coder clearly did things the wrong way :-(.

Wrong usage of volatile

In essence, the volatile qualifier marks an object or variable as ‘it can change outside of what the compiler might assume‘, both for read and write operations. And it should be only used for hardware registers.

Assuming the following (illustrative, non-hardware) example:

int var;

Whenever this variable is read, the next read operation might return something different.

Consider the following (simplified) example:

var = 5;
var++;

An optimizing compiler could combine the two operations into a single ‘var= 6;’ because previously it had stored the value of 5. If that variable would be marked with ‘volatile’

volatile int var;

then the compiler has to assume that the write might have side effects (changing other variables) or that a read of ‘var’ will not return what has been previously stored.

Now many developers wrongly reduce ‘volatile’ to ‘prevents compiler optimizations’ which is not the full story: yes, the net effect is kind of like that, but that not the full story.

So the compiler has to do read and writes whenever that variable is accessed, because things might have changed. Nothing more and nothing less. I highly recommend that you read “Nine ways to break your systems code using volatile” by John Regehr.

So don’t think you can ‘control’ the code beyond of telling the compiler that he needs to generate extra read and writes.

What are legitimate uses of volatile? The above example with ‘var’ for sure is not (was for illustrative usage only!).

A good usage of volatile is for hardware registers. Below is a simplified case for an Analog-to-Digital converter hardware which is memory mapped:

typedef struct {
  volatile uint32_t CTRL; /* ADC control register */
  volatile uint32_t VAL;  /* ADC result register */
} ADC;

The registers are marked as ‘volatile’, because the reading or writing to the control register will start or stop a conversion (has a hardware side effect), the register content might change anytime by the hardware, or some parts or bits of it are writable only and cannot be read. Similar to the VAL register which contains the conversion result: it can be changed anytime. So here the ‘volatile’ is appropriate because it warns the compiler about side effects and that the compiler cannot make any assumptions about read and write accesses. You still cannot make assumptions about how the read/write accesses are made (single 32bit? two 16bit accesses? 4 8-bit accesses? order of it?). Here only assembly code will be to the rescue.

And if you have seen volatile for inline assembly code:

__asm volatile("nop");

This is not wrong, but strictly speaking not needed, as the compiler shall not touch/change the assembly instruction. If you need it, then it is probably a sign of a compiler bug.

So in my view volatile should be used for hardware registers only, or in most cases. One use case where it is OK is something like this:

static volatile bool done = false;

void UART_Completed_Interrupt(void) {
  ....
  done = true;
}

void foo(void) {
  done = false;
  UART_SendString("hello world!"); /* shall trigger UART_Completed_Interrupt()! */
  while(!done) {
    /* wait, should add a timeout here! */
  }
}

The above assumes that read and write to ‘done’ is atomic, and that it is used just in the above instances and not somewhere else. While the above ‘works’, I rather would use a semaphore or other signalling.

There is another legitimate use case where ‘volatile’ has to be used to work-around a compiler bug, and where it is not appropriate to rewrite the code. In that case the ‘volatile’ could cause the compiler to skip (wrong) optimizations which in that (very rare) case is appropriate in my view.

I recommend that you search in your code base for the ‘volatile’ qualifier. You might be surprised in how many places it is used: if it is not used for hardware registers, it is very likely wrong.

That example below I showed earlier is such a case:

/* receive state structure */
typedef struct _debug_console_write_ring_buffer
{
    uint32_t ringBufferSize;
    volatile uint32_t ringHead;
    volatile uint32_t ringTail;
    uint8_t ringBuffer[DEBUG_CONSOLE_TRANSMIT_BUFFER_LEN];
} debug_console_write_ring_buffer_t;

The underlying problem is that the code using this ring buffer is not re-entrant and is not using the correct critical sections to make it re-entrant. Adding the ‘volatile’ hides the problem only: it makes the code ‘working’ in ‘most cases’ only, because it reduces the changes that things can go wrong: but again: it is simply wrong. Now: if you have not read “Nine ways to break your systems code using volatile” yet, that would be a good time. πŸ™‚

Again: I recommend that you have a look at the good previous discussion in “Spilling the Beans: Endless Loops“.

Happy volatiling πŸ™‚

19 thoughts on “Spilling the Beans: volatile Qualifier

  1. Good point about the reentrancy problem.

    What about memory which is affected by DMA? Surely by “hardware register” you mean any memory mapped location that can be altered by external events?

    Also, very interesting to read about the C on PDP-11… I wrote for PDP 11/73’s but only in assembler! those were the days! πŸ™‚

    Like

  2. Thank you for this nice article (as all yours about embedded programming .. keep writing).
    But I think that using volatile ONLY for registers is too conservative statement in bare-metal embedded systems. And John Regehr’s example (realy nice article) with interrupt software flag is what I mean – a very common situation in practice (that caused me headaches several times). So, especially this situation, must be kept always in mind.
    *****
    int done;

    __attribute((signal)) void __vector_4 (void) {
    done = 1;
    }

    void wait_for_done (void) {
    while (!done) ;
    }
    ****

    Liked by 1 person

    • Hi Yasen,
      thank you, and yes, this has been noted: indeed it is too restrictive, and in this case it would be fine. I think I need to add this to the article. Still I prefer a semaphore or similar so I don’t have to block, but this usually requires a runtime environment like an RTOS which is pretty standard for many embedded applications too, at least for the ‘medium’ or ‘larger’ ones.

      Like

  3. More great information, Erich, thankyou.
    My “rule of thumb” or my default assumption for volatile is this:

    If I’m writing a driver, then I will need volatile as some places in my code.
    If I’m writing control code that uses the driver, then I don’t need to use volatile in my code.

    Your next article is surely ‘static’ πŸ™‚

    Liked by 1 person

  4. I’m enjoying these daily refreshers on the basics.

    What about this common construct that I’ve been using forever?!

    volatile int done_flag;

    void ISR_TIMER (void) {
    done_flag = 1;
    }

    void wait_for_done (void) {
    while (!done_flag) ;
    // do something at regular interval
    }

    It’s also mentioned in the article you linked as a valid use in addition to hardware register access.
    – “The volatile qualifier forces stores to go to memory and loads to come from memory, giving us a way to ensure visibility across multiple computations (threads, interrupts, coroutines, or whatever).”

    Liked by 1 person

    • Hi Paul,
      yes, agreed, that’s for me one of the few cases where it is valid. However, one needs to keep in mind that it only works properly if only one is writing it, and only one is reading it, and if read and write operations are atomic.
      In general, I avoid such ‘interrupt polling flag’ as this is wasting CPU cycles. I rather use a synchronization with a semaphore or similar so I don’t have to wait.

      Like

    • I remember that in the Linux kernel volatile is almost strictly forbidden.

      If I recall correctly the argument was that it wasn’t variables what should be marked volatile, but particular reads or writes to a memory address, whose behaviour can be made more specific than just “volatile” (e.g. hardware specific caching behaviour, etc.) . And in some cases, volatile-like and non volatile accesses may make sense to be mixed.

      I found this on a quick search: https://www.kernel.org/doc/html/latest/process/volatile-considered-harmful.html

      Liked by 1 person

      • Thanks for that article link, really interesting. And it confirms for me that ‘volatile’ is overused and used in wrong places, just to ‘fix’ things in a in-proper way. For dealing with hardware caching it would be the wrong way too: there ‘sync’ barriers or ‘pipeline flush’ instructions are my choice solving these problems.

        Like

  5. Why do you think you need re-entrancy when you work with ring buffers?
    If you have multi-write point, you need to guard anyway, same for read.

    For me, its not the best example.

    Where I see a bigger problem is for example multiplication)

    int32_t
    square(volatile int16_t* in) {
    return (*in) * (*in);
    }

    Here result may not really be square.

    Liked by 1 person

  6. Here is another article to add to the reading list, and it is interesting that it came out about the same day this discussion started (Something in the Aehter?):

    “Preventing an optimising compiler from removing or reordering your code.”

    https://theunixzoo.co.uk/blog/2021-10-14-preventing-optimisations.html

    A separate issue where volatile can appear is ‘Code Motion’. The above discusses it in terms of C++ rather than the embedded level. John did address it to some degree in his article.

    When you see “The compiler is broken. My code works when I turn optimization off, my code runs fine.” in the messages boards you can be reasonably sure (I have found real compiler bugs in the early days of GCC-AVR) that there is either a missing volatile or an issue with code motion reordering things unexpectedly.

    In the foo() example I have never cared for the whole set/clear flag methodology. Consider a timer IRQ setting a ‘done’ flag. With the main loop clearing the flag, here can be race conditions setup such that IRQs can be missed. Because the timer IRQs are periodic, the fact that timer IRQs are being missed can be hard to notice. The error would show up as a long term time drift rather than an overt bug.

    A simple approach to the problem, if not RTOS is around with semaphores etc., is to increment a volatile event variable in the IRQ. Then in the main loop compare it with a last saved copy to see if the saved copy and the event variable are now different. This prevents the whole done set/clear race between the IRQ and the main loop. The event variable needs to be atomic sized for the processor at hand to not introduce other subtle bugs. There are also issues of IRQ rates vs main loop rates that need to be considered etc.

    Liked by 1 person

  7. The compiler optimization level can make these bugs hard to find. The answer to many of these problems is RTOS. Mostly I have decided that I only write RTOS programs…. that being said these multi cpu mcus can inject all-kinda-brain-damage.

    Liked by 1 person

    • Yes, that’s usually what I do. Using semaphore or other RTOS notification mechanism might seem like an overhead, but this all makes the ‘always works’ from a ‘might work with volatile’ thing. Again, volatile only should be used where it is the right thing, for example hardware registers.

      Like

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.