在古老的嵌入式世界中,人们将硬件(-配置)-寄存器映射作为结构编写,这是一个非常简单的32位硬件示例:
#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;
}
效果很好,编译器(至少在我们的平台上为gcc)意识到
hw_reg
引用了相同的地址(在编译时是已知且恒定的),并且ld
仅对其引用了一次。第二个st
(存储)是通过单指令的4字节偏移量完成的-同样在我们的平台上。如何在不使用
#defines
的情况下使用现代C++(在C++ 11之后)重现此行为?我们尝试了很多事情:类内外的
static const
和constexpr
。他们都不喜欢reinterprest_cast<>
的(隐式)。回应有关更改原因的评论:恐怕它主要是成名和荣耀。但不仅如此。使用此C代码调试可能很困难。假设您要记录所有的写访问,这种方法将要求您重写所有内容。但是,这里我不是在寻找可以简化特定情况的解决方案,而是在寻找灵感。
编辑只是为了澄清一些评论:我问这个问题是不要更改任何有效的代码(写于1990年代)。我正在寻找 future 项目的解决方案,因为我对
define
实现并不完全满意,并且在问自己现代C++是否有更好的可能性。 最佳答案
我认为变量模板为此处提供了一种优雅的解决方案。
// 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);
}
像这样使用它而不是与宏一起使用的好处之一是键入安全,并且实际上您可以引用同一源文件中不同硬件模块的寄存器,而无需混合使用。