In building libraries for controlling hardware on embedded microprocessors, a common task is manipulating bits at specific memory locations for controlling hardware features.
In AVR processors, Atmel (now Microchip) provides macros that expand to something like this:
#define PORTA (*(volatile uint8_t *)(0x25))
Which enables things like:
PORTA |= 1;
Now in C++11 (and newer), it is desirable to replace almost any usage of #define
with constexpr
.
In older versions of the GCC C++ compiler (4.9.2), the following compiled:
#include <avr/io.h>
constexpr volatile uint8_t *const PortA = &PORTA;
In version 8.2.0, the above does not compile and gives errors:
error: 'reinterpret_cast<volatile uint8_t* {aka volatile unsigned char*}>(37)' is not a constant expression
I'm not looking for explanations of why you cannot use reinterpret_cast
inside a constexpr
context or why integer to pointer conversion is illegal.
What is the correct way to have a constexpr
pointer to volatile
memory in modern C++?
I've seen suggestions of storing the memory address of PORTA
in a constexpr uintptr_t
and then reinterprect_cast
ing that to volatile uint8_t * const
at runtime for bit manipulation.
For instance, this works and even compiles to a single sbi
instruction in avr-gcc
as expected.
#include <stdint.h>
constexpr uintptr_t PortA = 0x25;
void set() { *((volatile uint8_t *)(PortA)) |= 1; }
However it takes a decent amount of ugly boilerplate to use PortA
as the pointer it is intended to be.
This also has the problem that it seem to be impossible to use the PORTA
macro directly. We're instead forced to hard-code the memory address 0x25
which breaks certain desirable portability features.
It feels like I'm missing something obvious buy my searches have not yielded anything fruitful.
For instance, this feels like an "address constant expression", but that seems to relate to referring to statically allocated const
values like which is not quite what I want.
const char str[] = "FooBar";
constexpr const char * x = str + 2;
error: reinterpret_cast from integer to pointer
. My compiler (g++ 7.3.0) spits out a different message -error: value ‘37’ of type ‘volatile uint8_t* {aka volatile unsigned char*}’ is not a constant expression
. Which is also very strange... – Waylonvolatile uint8_t& PortA = PORTA;
do what you want? – Ventoseconstexpr
number + runtimereinterpret_cast
trick you ruled out, but since they auto-generate these headers from SVD files, there isn't really a maintenance burden. – Anthropolatry