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.
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 the previously referenced 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 around parameters and return types.
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.
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.
I would recommend reading the original proposal Deprecating volatile for good explination of the use of volatile.
- Disassembling a Cortex-M raw binary file with Ghidra - December 20, 2022
- Using final in C++ to improve performance - November 14, 2022
- Understanding Arm Cortex-M Intel-Hex (ihex) files - October 12, 2022
Co-Founder and Director of Feabhas since 1995.
Niall has been designing and programming embedded systems for over 30 years. He has worked in different sectors, including aerospace, telecomms, government and banking.
His current interest lie in IoT Security and Agile for Embedded Systems.