Compiling the following, straightforward code:
volatile int x;
int main() {
x += 10;
}
https://godbolt.org/z/jq83vdvj5
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?
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.
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
static_assert(std::has_unique_object_representations_v<Port_t>);
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
|
as section 7.6.19.6 states:
- 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
// ...
}
or
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.
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.
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.