4

Coming from C and embedded background, register addresses are often used and cast to pointers:

#define REG_A_ADDR       0x80000000

uint32_t ptr_reg_a = (uint32_t*) REG_A_ADDR;

However, C++ emphasizes usage of constexpr for compile time constants and that is more typesafe.

The following is the equivalent version I came up with but it doesn't compile, since reinterpret_cast can't be used with constexpr, seemingly:

constexpr uint32_t reg_a_addr = 0x80000000;

constexpr uint32_t *ptr_reg_a = reinterpret_cast<uint32_t*>(reg_a_addr); // constexpr variable 'reg_a_addr' must be initialized by a constant expression

So, since the above snippet errors out, what's the practical approach around it? Using const compiles fine but is it practical?

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
xyf
  • 664
  • 1
  • 6
  • 16
  • The key word in your narrative "is more typesafe". Does this whole thing look typesafe to you? – Sam Varshavchik Jul 19 '22 at 00:17
  • https://stackoverflow.com/questions/10369606/constexpr-pointer-value – SuperStormer Jul 19 '22 at 00:23
  • @SamVarshavchik I don't see anything wrong with the types being used here. – xyf Jul 19 '22 at 00:25
  • Ignoring the issue of `constexpr` for a moment: without a cast of, some kind, this code will not compile. As such, a cast, here, defeats C++'s type-safety, by definition. – Sam Varshavchik Jul 19 '22 at 00:29
  • I'm confused. Are you asking me to write a code with main() and that actually runs in a compiler? Why do you think the cast here defeats type safety? – xyf Jul 19 '22 at 00:31
  • why can't `reinterpret_cast(2147483648)' ` be a constant expression? or rather a compile time? my actual question though is about an alternative code – xyf Jul 19 '22 at 00:42
  • 2
    [This](https://stackoverflow.com/a/59913380/4123703) might answer your question. I presume that standard doesn't want to allow a source of UB (`reinterpret_cast`) to be handled in compile time. In another way, it might be UB in runtime, so how to implement it in compile time which doesn't allow UB in all the cases? – Louis Go Jul 19 '22 at 00:51
  • @LouisGo That exactly answers OP question. Cheers! – akaAbdullahMateen Jul 19 '22 at 00:53
  • 1
    *"using `const` compiles fine but is it practical?"* -- You might have to define what you mean by "practical". I'd be inclined to say that using `const` is practical *because* it compiles fine, combined with it being not excessively long. Apparently you have a different definition of "practical" than me, so it would help if you included your definition in the question. – JaMiT Jul 19 '22 at 01:39
  • @akaAbdullahMateen I disagree. The link provided by Louis Go answers *why* this situation exists, but the question is *how* to work around this situation. It's a useful link, but it does not exactly answer the OP's question. – JaMiT Jul 19 '22 at 01:41

1 Answers1

2

Although I'm not entirely sure what you mean by "type safe"1,2 in this context, you can use const with the appropriate ...ptr_t type to ensure that the (constant) value you provide is valid for the pointer.

Take the following code, for example:

#include <iostream>
#include <cstdint>

// I added an extra zero to your constant - now it's too big for a 32-bit pointer
constexpr uintptr_t reg_a_addr = 0x800000000; // uintptr_t is platform-specific
uint32_t* const ptr_reg_a = reinterpret_cast<uint32_t* const>(reg_a_addr);
// ^ Note that "const unit32_t* ptr_reg_a ..." declares a pointer to a constant;
// the version I have given defines a constant pointer to (potentially) writable memory.

int main()
{
    std::cout << ptr_reg_a << "\n";
    return 0;
}

When targeting the x64 platform with MSVC, this compiles without warning and produces the expected output:

0000000800000000

However, when targeting the x86 (32 bit) platform, the compiler will issue warnings that the value is too big:

warning C4305: 'initializing': truncation from '__int64' to 'const uintptr_t'
warning C4309: 'initializing': truncation of constant value

And, indeed, the output will be the truncated value:

00000000

As to whether or not it is practical – well, it compiles but trying to use a constexpr with reinterpret_cast doesn't, so I guess it is!


Note that the clang-cl compiler in Visual Studio 2022 (when targeting 32 bits) gives an 'equivalent' warning for the constant being too big:

warning : implicit conversion from 'long long' to 'const uintptr_t' (aka 'const unsigned int') changes value from 34359738368 to 0 [-Wconstant-conversion]


1 Maybe, by "type safe," you mean that reassigning a new value to that pointer will be prevented at compile time … in which case: Yes, it is type safe, as code like the following will not compile:

    uint32_t q = 42;
    ptr_reg_a = &q;

error : cannot assign to variable 'ptr_reg_a' with const-qualified type 'uint32_t *const' (aka 'unsigned int *const')

2 If you are asking whether or not using const rather than constexpr will make such a pointer any less strictly typed, then: No, it won't. However, (IIRC) the C++ Standard does impose stricter requirements for diagnostics on potential misuse of constexpr values … but many (if not most) mainstream compilers will, if warnings are fully enabled, provide suitable diagnostics for cases of (say) violation of strict aliasing rules, or other usage of such a pointer that exhibits undefined behaviour.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83