Modern Embedded C++ - Deprecation of volatile

Compiling the following, straightforward code:

volatile int x;

int main() {
    x += 10;

Using g++, with the directive -std=c++17 builds without any warnings or errors. However, change the directive to -std=c++20, and the result is:

source>: In function 'int main()':
<source>:5:5: warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile]
    5 |   x += 10;
      |   ~~^~~~~
Compiler returned: 0

The new C++ standard, C++20, has deprecated volatile! So, what does this mean for the embedded programmer?

Don’t panic image

We covered the need for, and use of, volatile in a previous posting. That post (written in April 2020) did state that:

In C++20 many general uses of volatile are being deprecated.

The key phrase here is general uses.

Volatile in embedded

The good news is that the changes specified in the C++20 standard are, in most cases, unlikely to affect existing codebases.

The principal use of volatile in embedded is when access hardware registers. Here, we want to stop the compiler from optimizing away repeated reads or writes. The changes mainly apply to volatile expressions with multiple side effects (see previous post if this is unclear).

So, for example, simple reads or writes to a volatile object will not cause compiler warnings.

The most likely idiomatic code that will generate warnings is the setting and clearing of bits within a register, e.g.

#include <cstdint>
#include <type_traits>

struct Port_t {
     volatile  std::uint8_t ctrl;
     volatile  std::uint8_t cfg;
     volatile  std::uint8_t data;
     volatile  std::uint8_t status;

// ensure no padding

constexpr unsigned address{0x40020000U};
Port_t* const port { reinterpret_cast<Port_t*>(address) };

void fn()
  port->ctrl |= 0x3;   // set bits 0 and 1 of ctrl reg 

Compiled with std=c++20 will generate the warning

<source>: In function 'void fn()':
<source>:15:14: warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile]
   15 |   port->ctrl |= 0x3;   // set bits 0 and 1 of ctrl reg

link to example

as section states:

  1. The behavior of an expression of the form E1 op= E2 is equivalent to E1 = E1 op E2 except that E1 is evaluated only once. Such expressions are deprecated if E1 has volatile-qualified type;

Annex D.6 details all the cases where the use of volatile types have been deprecated. In addition to the compound expression, any use of prefix or postfix ++ or -- will also generate a warning. There are a couple of other cases where the use of volatile is deprecated.

To eliminate the error requires a simple refactoring to either:

void fn()
    //   ...
  port->ctrl = port->ctrl | 0x3;   // set bits 0 and 1 of ctrl reg 
    //   ...


void fn()
  auto reg = port->ctrl;  // read volatile 
  reg  |= 0x3;            // modify value
  port->ctrl = reg;       // write to volatile

depending on your preferred style.

Examining the generated assembler code using Arm gcc 10.2.1 shows no difference between the examples.

An aside – the register keyword

While discussing deprecated features, C++17 removed the keyword register as a storage specifier. The long-term goal is to repurpose it, as auto was, in a future revision. Obviously, this would potentially be very useful in the embedded space.

I would be surprised (nay shocked) if you were using register as a storage class specifier in your code today. That said, on a recent project using the Nordic nRF5 SDK, the particular version v15.3.0 uses register within the supplied gcc CMSIS headers (V4.30), e.g.

/opt/nRF5_SDK_15.3.0_59ac345/components/toolchain/cmsis/include/cmsis_gcc.h: In function 'uint32_t __get_PSP()':
/opt/nRF5_SDK_15.3.0_59ac345/components/toolchain/cmsis/include/cmsis_gcc.h:150:21: warning: ISO C++17 does not allow 'register' storage class specifier [-Wregister]
  150 |   register uint32_t result;

Usefully, the use of register has been removed from later versions of CMSIS.

Note that register is still a reserved keyword in both C++17 and C++20.

In summary

The depreciation of volatile is not a headline change in C++20; it should not affect your existing codebases but may generate new warnings. Assuming you are using best practice and have the compiler option -Werror "Make all warnings into errors" set, then this simple change of standard will now cause a build failure.

Finally, when developing new code, whether in C or C++, prefer to use the explicit “read-modify-write” to compound statements.