【问题标题】:Testing template to memory location to replace defines in embedded systems测试模板到内存位置以替换嵌入式系统中的定义
【发布时间】:2025-12-20 08:10:12
【问题描述】:

在嵌入式系统中,您通常有一个内存位置,它不在程序内存本身内,而是指向一些硬件寄存器。大多数 C SDK 将这些作为#define 语句提供。根据以下文章,https://arne-mertz.de/2017/06/stepping-away-from-define/ 一种从 #define 语句(由 C SDK 使用)转换为对 C++ 更友好的方法是创建一个强制 reinterpret_cast 在运行时发生的类。

我试图以稍微不同的方式来解决这个问题,因为我希望能够为不同的指针创建“类型特征”。让我用一个例子来说明。

#define USART1_ADDR 0x1234
#define USART2_ADDR 0x5678

template <typename T_, std::intptr_t ADDR_>
class MemPointer {
public:
    static T_& ref() { return *reinterpret_cast<T_*>(ADDR_); }
};

class USART {
public:
    void foo() { _registerA = 0x10; }

private:
    uint32_t _registerA;
    uint32_t _registerB;
};

using USART1 = MemPointer<USART, USART1_ADDR>;
using USART2 = MemPointer<USART, USART2_ADDR>;

template <typename USART_>
class usart_name;

template <>
class usart_name<USART1> {
public:
    static constexpr const char* name() { return "USART1"; }
};

template <>
class usart_name<USART2> {
public:
    static constexpr const char* name() { return "USART2"; }
};

此示例中的每个 USART“实例”都是其自己的唯一类型,因此我能够创建特征,允许在编译时“查找”有关 USART 实例的信息。

这实际上似乎工作,但是,我想创建一些测试代码如下

static USART testUsart;

#define TEST_USART_ADDR (std::intptr_t)(&testUsart);

using TEST_USART = MemPointer<USART, TEST_USART_ADDR>;

失败并出现以下错误:

从指针类型'USART*'到算术类型的转换 'intptr_t' {aka 'long long int'} 在常量表达式中

我相信我根据Why is reinterpret_cast not constexpr?了解问题的根源

我的问题是,有没有办法让我的 MemPointer 模板也适用于上面的测试代码?

编辑

一种解决方案是为每个“实例”设置一个单独的类

class USART1 : public USART {
public:
    static USART& ref() { return *reinterpret_cast<USART*>(USART1_ADDR); }
};

class USART2 : public USART {
public:
    static USART& ref() { return *reinterpret_cast<USART*>(USART2_ADDR); }
};

我更喜欢某种模板+使用组合,但我不需要编写一堆类。但也许这是唯一的选择。

【问题讨论】:

  • 你能把它改成:static constexpr USART testUsart; 吗?
  • 您是否有令人信服的理由将这些地址设为模板参数,而不是运行时参数?您的问题似乎集中在使变量的运行时地址成为编译时常量。
  • 无关:为什么用#defines 而不是constexpr std::uintptr_t USART1_ADDR = 0x1234; constexpr std::uintptr_t USART2_ADDR = 0x5678;
  • @TedLyngmo 定义来自嵌入式系统制造商提供的 C SDK。基本上,我正在围绕一些已经提供的代码编写一个轻量级的 C++ 层。但我同意,理想情况下你会直接使用 constexpr。
  • @DrewDormann 你的措辞很有意义。本质上,外设地址也是运行时的。我最初为每个单独的 USART“实例”创建了一个单独的类,并使用一个静态 instance() 函数返回地址的转换版本。我试图将其作为模板来避免所有重复的类代码。

标签: c++ templates embedded


【解决方案1】:

有没有办法让我的 MemPointer 模板也适用于上面的测试代码?

你可以停止坚持地址是intptr_t。无论如何,您都要把它转换成一个指针,那么为什么不只允许存在该转换的任何类型呢?

template <typename T_, typename P, P ADDR_>
class MemPointer {
public:
    static T_& ref() { return *reinterpret_cast<T_*>(ADDR_); }
};

using USART1 = MemPointer<USART, std::intptr_t, USART1_ADDR>;
using USART2 = MemPointer<USART, std::intptr_t, USART2_ADDR>;

static USART testUsart;
using TEST_USART = MemPointer<USART, USART*, &testUsart>;

后续说明:

  • 如果这是供其他人使用的库,我会考虑在 MemPointer 中添加一个 static_assert(std::is_trivial_v&lt;T_&gt;) 以捕获烦人的错误

  • 围绕填充和对齐等问题存在一些潜在问题,但我假设您知道您的特定嵌入式平台在做什么

  • 您应该对您的注册成员或整个对象进行 volatile 限定(例如,您可以从 MemPointer::ref 返回 std::add_volatile_t&lt;T_&gt;&amp;

    这样编译器就知道每次写入都是可观察到的副作用(即,即使您的程序从不回读,硬件也可以观察到),并且每次读取都可能产生不同的值(因为硬件可以更新即使你的程序没有)。

【讨论】:

  • 使用volatile不仅“明智”,而且绝对是强制性的。
  • 我试图专注于眼前的问题,但你是对的,这只是误导