References to memory mapped registers

Asked by Michael Steinberg on 2014-12-13

Hello, I just tried to uprade to 4.9 and ran into a problem because it enforces the standard more strictly. With 4.8 following construct compiled nicely and did what I intended it to:

 template< typename T, unsigned intaddress >
 struct reference
  static constexpr T& value = *reinterpret_cast<T*>(address);

With 4.9 this does not compile, since reinterpret_cast is not a const expression according to standard. So all I can do is use a static const member with out-of-class initialization (ie. runtime initialization etc.). Is there any extension in place in this compiler port that could make the old code work again? I basically only want to map registers to references in a C++-way. I am totally aware that this is platform dependent, but the addresses are anyways so I don't see why one shouldn't be able to do this.

Best regards

Question information

English Edit question
GNU ARM Embedded Toolchain Edit question
No assignee Edit question
Last query:
Last reply:
Terry Guo (terry.guo) said : #1

I failed to reproduce your issue, below small case works fine to me, seems no compiler error about this from 4.9 release:

terguo01@terry-pc01:tmp$ cat x.cpp
template< typename T, unsigned int address >
struct reference
  static constexpr T& value = *reinterpret_cast<T*>(address);

foo ()
  struct reference<int, 0x10000000> x;

arm-none-eabi-g++ -mthumb -mcpu=cortex-m3 -O0 -S x.cpp -std=c++11

Michael Steinberg (decimad) said : #2

thank you for trying, I'm currently confused why this works for you. I'm currently thinking that because "value" is not referenced, the constexpr expression is not evaluated?
Can you give "reference<int, 0x1000000>::value = 5;" or "auto& ref = reference<int, 0x1000000000>::value;" a try? The standard really forbids reinterpret_cast in constexpr expressions (I learned it the hard way), but 4.8 allowed it and with 4.9 I didn't see any way anymore. I downloaded the latest stable version from the main page.

Michael Steinberg (decimad) said : #3

Okay I'm at my machine again, I'm trying to collect all relevant info.

[source code source.cpp]
template< typename T, unsigned int address >
struct reference
 constexpr static T& value = *reinterpret_cast<T*>(address);

auto& ref = reference< int, 0x43 >::value;
[/source code]

[command line]
D:\gnu_arm_test>arm-none-eabi-g++ -mthumb -mcpu=cortex-m3 -O0 -S source.cpp -std=c++11
source.cpp: In instantiation of 'constexpr int& reference<int, 67u>::value':
source.cpp:8:37: required from here
source.cpp:5:22: error: reinterpret_cast from integer to pointer
  constexpr static T& value = *reinterpret_cast<T*>(address);
[/command line]

D:\Projekte\Projektarbeit\gnu_arm_test>arm-none-eabi-g++ -v
Using built-in specs.
gcc version 4.9.3 20141119 (release) [ARM/embedded-4_9-branch revision 218278] (GNU Tools for ARM Embedded Processors)

Marc Singer (eleventen) said : #4

I admit I may be missing something. What is wrong with a templated function? I can see how the templated structure might be convenient if it could be statically initialized. I wasn't successful in doing so. The following templated function works OK for me. Also know that I'm doing the same thing you are in defining structures to access register files.

template <typename T_, uint32_t address> constexpr T_& ioref () {
  return *reinterpret_cast<T_*>(address); }

I checked the assembled code and it's optimal. There is no function call made.

Marc Singer (eleventen) said : #5

I just realized that I should show it in use:

  auto p = ioref<IOSTRUCT,0xa000>();
  p.x = 2;
  ioref<IOSTRUCT,0xa000>().y = 4;

Michael Steinberg (decimad) said : #6

Hello Marc,
thank you for hopping in! What I'm trying to do is giving a named reference a compile time static target address. I'm trying to avoid macros where possible. The way you suggest is in my eyes pretty close to the reinterpret_cast/C-Cast-Macros from the vendors. The thing is we must do a (runtime-)call at the place of access and rely on the optimizer to figure it out, since we cannot do the cast at compile-time.

My use-case with 4.8 is:
static auto& some_register = reference<...>::value;

From then on I can use "some_register" as a short identifier that respects namespaces and everything (actually it will be more like a struct for multiple registers). So right now I have the option to stick with 4.8 or accomplish it with custom symbol tables for the linker (since I started to auto-generate the headers from a register meta-file, the latter will actually not be sooo super bad). But since this port of the compiler is targetted at a microcontroller and hardware access is bread&butter I thought there may be extensions in place that could make it possible to achieve this within the language.

Kind regards

Marc Singer (eleventen) said : #7


I've been thinking more about this as well. I agree that the function template is more like the macro than we'd like. As a constexpr, it should avoid a runtime penalty. As I wrote before, I checked (some) of the assembler output and found that it was doing what we'd hope with a simple address load from the constant pool.

That said, I'd like what you are talking about and then some. I'd really like to be able to write:

     using SATA = iostruct<SATA_STRUCT,0xa0010000>;
     SATA::p.config = 10; // Of course, I'd like to be able to drop the ::p and use SATA.config
     using UARTx = iostruct<UART_STRUCT>;
     using UART1 = UARTx<0xa0020000>;

I'm currently using a macro to bridge the gap which satisfies the code readability, but isn't as C++ as I'd like.

The form you are using makes me think it may allocate RAM for each register to hold some_register. Have you verified that there is no RAM/ROM impact per instance?


A value of any integral or enumeration type can be converted to a pointer type. A pointer converted to an integer of sufficient size and back to the same pointer type is guaranteed to have its original value, otherwise the resulting pointer cannot be dereferenced safely. The null pointer constant NULL or integer zero is not guaranteed to yield the null pointer value of the target type; static_cast or implicit conversion should be used for this purpose.

So, I'm inclined to believe this is a bug.

FWIW, I spent some time attempting to coerce [sic] the behavior we're looking for without any luck.


Michael Steinberg (decimad) said : #8

Well, you're right, I'm still relying on the optimizer to optimize away the store for the reference (although that is not so improbable), but since I specify internal linkage, the compiler has all rights to eliminate it in my eyes, as it does with function-local alias-references.
I cannot quote from the standard, but the definition of constexpr actively and namely excludes reinterpret_cast in valid constexpr expressions.

The only solution that is supported by standard would be to declare the symbols "extern "C" register_type myregister;" and let the linker supply placement through symbol files "-Wl symbols.sym". symbols.sym then contains many "myregister = 0xFooFoo;".

Kind regards

Marc Singer (eleventen) said : #9


I looked at the compiler output for the following:

    template <typename T_, uint32_t address> constexpr T_& ioref () {
      return *reinterpret_cast<T_*>(address); }
    template <typename T_, uint32_t address>
    struct iostruct {
      static constexpr T_& p = *(T_*) ((void*)address);

    static auto& p1 = iostruct<STM32::RCC,0xa0002000>::p;
    static auto& p2 = ioref<STM32::RCC,0xa0003000>();

    void testing () { = 1; = 1;

The compiler output is identical for both p1 and p2 with the 4.8 compiler.

Unfortunately, the 4.9 compiler creates a static datum for the p2 form. Darn it. my hunch is that t would do the same for the p1 form if were willing to accept it. IMHO, the creating of a static datum makes the static object unusable.


Michael Steinberg (decimad) said : #10

Yes indeed, since the 4.9 compiler rejects thinking that a reinterpret_cast can be a constant expression (following the standard, which I think is too strict in this point) there seems to be no "constant propagation" or how one could call that... It's a shame really.

Launchpad Janitor (janitor) said : #11

This question was expired because it remained in the 'Open' state without activity for the last 15 days.