Какой современный С++ способ использовать абсолютные адреса для переменных указателя?
Во встроенном мире люди на протяжении веков писали аппаратные (-конфигурации) -регистрации-сопоставления как структуры, очень простой пример для 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", ®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);
}
Одно из преимуществ использования этого, а не макросов заключается в том, что вы безопасны по типу, и вы можете фактически ссылаться на регистры разных аппаратных модулей из одного и того же исходного файла, не смешивая все это.
Ответ 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;