¿Cuál es la forma moderna de C++ de convertir direcciones absolutas a variables de puntero?
c++11 embedded (2)
En el mundo incrustado durante siglos, las personas escribieron hardware (-configuración) -registrar-asignaciones como estructuras, un ejemplo muy simple para un hardware de 32 bits:
#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;
}
Esto funciona muy bien, el compilador (gcc al menos en nuestra plataforma) reconoce que hw_reg
hace referencia a la misma dirección (que se conoce y es constante en el momento de la compilación) y la está ld
solo una vez. La segunda (tienda) se realiza con un desplazamiento de 4 bytes con una sola instrucción, nuevamente en nuestra plataforma.
¿Cómo reproducir este comportamiento con C ++ moderno (post C ++ 11) sin usar #defines
?
Probamos muchas cosas: static const
dentro y fuera de las clases y constexpr
. A ambos no les gustan (implícitos) reinterprest_cast<>
''s.
Respondiendo a un comentario sobre por qué cambiarlo: me temo que es sobre todo fama y gloria. Pero no solo. Con este código C la depuración puede ser difícil. Imagine que desea registrar todos los accesos de escritura, este enfoque requeriría que reescriba todo en todas partes. Sin embargo, aquí no estoy buscando una solución que simplifique una situación específica, estoy buscando inspiración.
EDITAR Solo para aclarar según algunos comentarios: estoy haciendo esta pregunta para no cambiar ningún código que esté funcionando (y fue escrito en la década de 1990). Estoy buscando una solución para futuros proyectos, porque no estoy totalmente satisfecho con la define
implementación, y me preguntaba si el C ++ moderno tiene una posibilidad superior.
Creo que las plantillas variables hacen una solución elegante aquí.
// 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);
}
Una de las ventajas de usarlo de esta manera en lugar de usar macros, es que está seguro y puede consultar los registros de diferentes módulos de hardware del mismo archivo fuente sin mezclarlos todos.
Dado que el único propósito de #define
es darle acceso a los miembros de la estructura, puede usar una plantilla para hacer el equivalente. Mi compilador genera código para la plantilla que es idéntica a #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;