Какой современный С++ способ использовать абсолютные адреса для переменных указателя?

Во встроенном мире люди на протяжении веков писали аппаратные (-конфигурации) -регистрации-сопоставления как структуры, очень простой пример для 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 (store) выполняется с 4-байтным смещением с одной инструкцией - снова на нашей платформе.

Как воспроизвести это поведение с помощью современного С++ (post С++ 11) без использования #defines?

Мы пробовали много вещей: static const внутри и снаружи классов и constexpr. Они оба не любят (неявные) reinterprest_cast<>.

Отвечая на комментарий о том, зачем его менять: я боюсь, что это в основном слава и слава. Но не только. С этой отладкой кода C может быть сложно. Представьте, что вы хотите регистрировать все обращения к записи, этот подход потребует, чтобы вы переписывали все повсюду. Однако здесь я не ищу решение, которое упростит конкретную ситуацию, я ищу вдохновение.

РЕДАКТИРОВАТЬ. Чтобы уточнить некоторые комментарии: я задаю этот вопрос, чтобы не менять какой-либо код, который работает (и был написан в 1990-х годах). Я ищу решение для будущих проектов, потому что я не совсем доволен реализацией define и спрашивал себя, обладает ли современный С++ превосходной возможностью.

Ответы

Ответ 1

Я думаю, что переменные шаблоны делают здесь элегантное решение.

// 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 header
struct MyHW : HardwareAt<0x10000000> {
    std::uint32_t in;
    std::uint32_t out;
};

// Example usage
int main()
{
    std::printf("%p\n%p\n", &regs<MyHW>.in, &regs<MyHW>.out);

    // or with alias for backward compatibility:
    auto hw_reg = &regs<MyHW>;
    std::printf("%p\n%p\n", &hw_reg->in, &hw_reg->out);
}

Одно из преимуществ использования этого, а не макросов заключается в том, что вы безопасны по типу, и вы можете фактически ссылаться на регистры разных аппаратных модулей из одного и того же исходного файла, не смешивая все это.

Ответ 2

Поскольку единственная цель #define - предоставить вам доступ к членам структуры, вы можете использовать шаблон для выполнения эквивалента. Мой компилятор генерирует код для шаблона, который идентичен #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;