Memfault Beyond the Launch

The volatile keyword

Colin WallsApril 1, 20244 comments

I have often talked and written about my thoughts on programming languages. In particular, the fact that there really is no language that was designed with embedded programming specifically in mind. Historically, there was assembly language, but that is rarely used for large scale coding nowadays. Intel promoted PL/M many years ago, but that has long since fallen by the wayside.

C and C++ are the key languages today. Neither was designed for embedded, but they do the job quite well. Ironically, I think that one of the precursors to C, BCPL, would have been well suited to embedded work, but that language has also been consigned to the history books.

There is one feature of C/C++, that is often not well understood, but is a godsend to embedded programmers: the keyword volatile

This article is available in PDF format for easy printing

When you declare a variable/object volatile, you are telling the compiler not to optimize access to the data. When your code writes a value to the variable, it should be written straight away and not saved in a register for use later. Likewise, when the code reads the value, it should not use a copy that was obtained earlier.

Broadly speaking, it must be assumed that a volatile variable can change at any time, independently of the current code. This implies two uses: variables that are shared between execution threads (between tasks in an RTOS or between the mainline code and an interrupt service routine); I/O device registers.

Although volatile is a useful language facility, it is not without difficulties. Firstly, some research has shown that many compilers do not implement the keyword correctly in all circumstances. So care and attention to generated code is required. There are also the usual troubles with C syntax; consider these:

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*

Plenty of room for errors there! (The same syntactic issues arise with const.)

However, just declaring a variable volatile is not enough if you are sharing it between execution threads (even if there are texts that suggest that this is the case). To illustrate the problem, consider this code:

volatile x = 1;

Syntactically this is fine, but what about the generated code? If the CPU instruction set allows a memory location to be incremented directly with a single, non-interruptible instruction, there is no problem (so long as the compiler uses those instructions!). But many devices would require the data to be read, incremented and written back. This is all fine according to the language definition, but what if an interrupt occurs during this sequence of instructions?

You need to protect access to it so that the memory location always contains a valid value. You could do this by disabling and re-enabling interrupts or, with an RTOS, maybe a semaphore would be the answer.

Memfault Beyond the Launch
[ - ]
Comment by lfolladoreApril 11, 2024

Dear Colin,

First of all, thank you for your articles.

I would like to report you some inaccuracies I found in your examples.

In your comments, I would have read "pointer to int" instead of "pointer to int*"

Later, I would have read "volatile int x = 1;" instead of "volatile x = 1;"

Beyond this, your article's title gave me the hope to find answers to some of my old questions but unfortunately this version is for the moment too light to this end.

In fact, if you or someone has both knowledge and time, I am still looking for an up to date article that would summarize the concepts behind "volatile" keyword, atomic operations and memory barriers.

This would ideally exemplify their correct usage to achieve different goals ranging from simple signaling to structure or large buffer passing, may it be between IRQ and a task or between tasks when a DMA is involved or not.

The different covered contexts could be bare metal, RTOS on (single/multi core) microntrollers or even Linux driver and userland application, all for use in embedded systems.

When (RT)OS are involved, it would certainly explain how they already offer (some of) the required services through their synchronisation mechanisms.

To be pragmatic, it could perhaps take the form of a library of patterns classed by goal and context.

Has anyone already written such a guide or would be interested in reading or writing (part of) it?

[ - ]
Comment by ColinGrantApril 10, 2024

I'd add that some of 'volatile' is deprecated.


[ - ]
Comment by aaronbauchApril 10, 2024

It would be helpful to identify the concept the author is struggling with as the second use of "volatile" as Atomic.  These are NOT the same thing but often confused.  When two threads or resources share a common item (like a variable or flag) these MUST be accessed with Atomic operations, which has nothing to do with volatile data.

[ - ]
Comment by tcfkatApril 12, 2024

Well, this depends if the said shared data is accessed in a single, indivisible instruction. Say a read-modify-write cycle of 8 bit data in a non-interruptible instruction. Then, atomic is not necessary.

In all other cases these accesses should be made atomic.

To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Please login (on the right) if you already have an account on this platform.

Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: