In the embedded world for ages people wrote hardware(-configuration)-register-mappings as structures, a really simple example for a 32-bit hardware:
#define hw_baseaddr ((uintptr_t) 0x10000000)
struct regs {
uint32_t reg1;
uint32_t reg2;
};
#define hw_reg ((volatile struct regs *) hw_baseaddr)
void f(void)
{
hw_reg->reg1 = 0xdeadcafe;
hw_reg->reg2 = 0xc0fefe;
}
This works very well, the compiler (gcc at least on our platform) recognizes that the hw_reg is referencing the same address (which is known and constant at compile-time) and is ld'ing it only once. The second st (store) is done with a 4-byte-offset with a single instruction - again on our platform.
How to reproduce this behavior with modern C++ (post C++11) without using #defines?
We tried a lot of things: static const inside and outside classes and constexpr. They both don't like (implicit) reinterprest_cast<>'s .
Responding to a comment as to why changing it: I'm afraid it's mostly fame and glory. But not only. With this C code debugging can be hard. Imagine you'd want to log all write-accesses, this approach would require you to rewrite everything everywhere. However, here I'm not looking for a solution which will simplify a specific situation, I'm looking for inspiration.
EDIT Just to clarify as per some comments: I'm asking this question not to change any code which is working (and was written in the 1990s). I'm looking for a solution for future projects, because I'm not totally happy with the define-implementation, and was asking myself whether modern C++ has a superior possibility.
I think variable templates make for an elegant solution here.
// Include this in some common header
template <class Impl>
volatile Impl& regs = *reinterpret_cast<volatile Impl*>(Impl::base_address);
template <std::uintptr_t BaseAddress>
struct HardwareAt {
static const std::uintptr_t base_address = BaseAddress;
// can't be instantiated
~HardwareAt() = delete;
};
// This goes in a certain HW module's header
struct MyHW : HardwareAt<0x10000000> {
std::uint32_t in;
std::uint32_t out;
};
// Example usage
int main()
{
std::printf("%p\n%p\n", ®s<MyHW>.in, ®s<MyHW>.out);
// or with alias for backward compatibility:
auto hw_reg = ®s<MyHW>;
std::printf("%p\n%p\n", &hw_reg->in, &hw_reg->out);
}
One benefit of using it like this instead of with macros, is that you're type safe, and you can actually refer to registers of different hardware modules from the same source file without mixing it all up.
Since the sole purpose of the #define is to give you access to struct members, you could use a template to do the equivalent. My compiler generates code for the template that is identical to the #define.
// #define hw_reg ((volatile struct regs *) hw_baseaddr)
template <class T, uintptr_t addr>
class RegsPtr
{
public:
RegsPtr() { ; }
volatile T* operator->() const { return reinterpret_cast<T*>(addr); }
volatile T& operator*() const { return *operator->(); }
};
const RegsPtr<struct regs, hw_baseaddr> hw_reg;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With