【问题标题】:How to create a constexpr pointer to a register on embedded system如何在嵌入式系统上创建指向寄存器的 constexpr 指针
【发布时间】:2021-07-06 07:04:36
【问题描述】:

我希望能够配置一个类,使其能够在其成员函数中访问硬件。假设我们有一个 avr 设备,我们可以简单地访问像 PORTA = 0x00; 这样的硬件,它将0x00 写入 io 内存空间。这个问题对于每种嵌入式内存 io 访问都是通用的,而不是特定于 avr。

但是如果我现在想使用一个可以参数化的类,C++ 似乎已经关闭了所有的大门,因为似乎根本不可能定义任何类型的指针类型并给它们一个 constexpr 值。

之前的一些编译器版本,我们能够运行这样的代码:constexpr reference to avr port address

但是现在所有尝试为 constexpr 值的指针分配值都失败了,因为在这种情况下不能再使用 reinterpret_cast

作为一个肮脏的黑客,我尝试过但失败了:

struct ONE 
{
    static constexpr volatile uint8_t* helper=nullptr;
    static constexpr volatile uint8_t* portc=&helper[100];
};

失败:

x.cpp:6:57: error: arithmetic involving a null pointer in '0'
    6 |     static constexpr volatile uint8_t* portc=&helper[100];

同样失败:

 // for AVR PORTB is defined in io.h like:
 #define PORTB (*(volatile uint8_t *)((0x05) + 0x20))

 constexpr volatile uint8_t* ptr=&PORTB;

失败:

x.cpp: In function 'int main()':
x.cpp:15:37: error: 'reinterpret_cast<volatile uint8_t* {aka volatile unsigned char*}>(56)' is not a constant expression
   15 |     constexpr volatile uint8_t* ptr=&PORTB;

直接让我找到reinterpret_cast<volatile uint8_t*>(37)' is not a constant expression。而且还没有任何解决办法!

我的目标非常非常简单: 编写一些可以配置为使用特定寄存器的类,例如:

template < volatile uint8_t* REG>
class X
{
    public:
        X() { REG = 0x02; }
};

如果我们不能再将指针值定义为 constexpr 值,我们就不能在模板中使用它们,也不能直接使用它们。这意味着,我们只有运行时变量,无法再优化,并且总是需要 ram 闪存中的空间。这对于非常小的嵌入式系统是不可接受的。

如果事实确实如此,那么唯一的工作方式就是使用 c-macros 吗?我不敢相信我的任何代码都不会再工作了......而且如果不使用 C 宏,将来也永远不会工作。

我目前使用avr-g++ (Fedora 10.2.0-1.fc33) 10.2.0,但从我的所有读数看来,如果在 C++17 模式下使用它是正确的行为。

【问题讨论】:

  • 您使用什么确切的编译器和版本?是最近GCC吗?还有哪些编译器选项?
  • @BasileStarynkevitch avr-g++ (Fedora 10.2.0-1.fc33) 10.2.0 with c++17

标签: c++ pointers constexpr


【解决方案1】:

我认为这在 C++17 中根本不可能。但是,如果您愿意切换到 C++20,则有一种可能的解决方法。与其直接伪造constexpr 指针,不如创建一个自定义类型来表示它,当constexpr 上下文离开时,它将在第一时间转换为指针:

#include <cstdint>

template <typename T>
struct fixed_ptr {
    std::uintptr_t m_value;

    inline constexpr fixed_ptr(std::uintptr_t p): m_value(p) {}

    inline operator T * () const {
        return reinterpret_cast<T *>(m_value);
    }
};

那么不要直接为模板参数指定类型,而是通过一个概念来约束它:

#include <utility>
#include <cstdint>

template <typename T, typename U>
concept pointerish = std::is_same_v<U &, decltype(*std::declval<T>())>;

template <pointerish<volatile std::uint8_t> auto REG>
class X {
    public:
        X() { *REG = 0x02; }
};

volatile std::uint8_t x;

auto p = X<fixed_ptr<volatile std::uint8_t>(5)>();
auto q = X<&x>();

-O1,使用类包装器而不是直接指针应该没有开销:内联会处理它。

现在,如何处理现有的预处理器宏?您不能将地址转换回uintptr_t,因为这构成了reinterpret_cast,这在constexpr 上下文中是被禁止的。您可以在头文件上运行 gcc -E -dM 以提取宏作为额外的构建步骤:

echo '#include <avr/io.h>' |
    gcc -E -dM - | 
    sed -ne '
        s!#define \(PORT[A-Za-z0-9_]*\) ( *\* *( *\(.*\) *\*)\(.*\))!DEF_PORT(\1, \2, \3)!p' \
    > avr-io.hh

然后您可以使用生成的文件来创建自己的 C++ 兼容头文件,如下所示:

#define DEF_PORT(name, type, addr) \
    constinit fixed_ptr<type> name = addr;
#include "avr-io.hh"
#undef DEF_PORT

但是,如果您不想添加构建步骤,那么总会有肮脏的预处理器技巧来拯救:

#define volatile ), (
#define UNWRAP_ADDR2__(x) 
#define UNWRAP_ADDR1__(x) UNWRAP_ADDR2__ x
#define UNWRAP_ADDR0__(x, y) y
#define UNWRAP_ADDR(x) UNWRAP_ADDR1__(UNWRAP_ADDR0__ x)

constexpr std::uintptr_t ADDR_PORTA = UNWRAP_ADDR(PORTA);
constexpr std::uintptr_t ADDR_PORTB = UNWRAP_ADDR(PORTB);

#undef volatile

这里我们利用了寄存器宏定义的形式为(*(volatile XXX *)(YYY))这一事实。将volatile 定义为), ( 将寄存器宏拆分为UNWRAP_ADDR0__ 的两个宏参数,从而去掉了包含取消引用运算符的“第一个参数”,而UNWRAP_ADDR1__UNWRAP_ADDR2__ 去除了类型的其余部分投掷。您只剩下数字地址。

【讨论】:

  • 我可以切换到c++20,没问题。但我仍然不知道如何在这种情况下使用预定义的 Io 寄存器定义。应该可以使用:X&lt; PseudoPointer&lt; uint8_t &gt;(&amp;PORTA) &gt; x; 这目前与error: 'reinterpret_cast&lt;volatile uint8_t* {aka volatile unsigned char*}&gt;(59)' is not a constant expression 失败并让我回到起点......有什么想法吗?
  • @Klaus PORTA 是什么?
  • 请看我的问题。对于 AVR,所有 IO 寄存器在一些头文件中都有一个定义来访问寄存器。正如我的问题所写,PORTA 是:#define PORTB (*(volatile uint8_t *)((0x05) + 0x20))
  • @Klaus 那是PORTB。而且,好吧,预计它不起作用。你仍然不能在constexpr 上下文中执行reinterpret_cast 指向指针(或任何等效转换)的整数。我的回答只是提供了一个包装器,您可以使用 而不是 强制转换。
  • PORTA 和 PORTB 是唯一具有不同值/地址的。在这种情况下不相关。如果我可以输入一个整数,您的回答提供了一个包装器,但我有来自 avr 库的“PORTX”定义。是的,有两个问题:首先:将这些定义用作任何包装器的输入,并将包装器用作某个寄存器的输出。你解决了最后一个,第一部分仍然开放。那是我的问题。正如您所注意到的:C++ 根本不允许在 constexpr 中进行强制转换。但这意味着:使用 C 宏,这在我看来是可怕的。感觉像是语言错误!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-03
  • 1970-01-01
  • 2021-06-10
  • 1970-01-01
  • 2021-02-14
  • 2020-10-25
  • 2023-03-12
相关资源
最近更新 更多