La cuestión
En un contexto incrustado bare-metal de bajo nivel , me gustaría crear un espacio en blanco en la memoria, dentro de una estructura C ++ y sin ningún nombre, para prohibir al usuario acceder a dicha ubicación de memoria.
En este momento, lo he logrado poniendo un uint32_t :96;
campo de bits feo que ocupará convenientemente el lugar de tres palabras, pero generará una advertencia de GCC (campo de bits demasiado grande para caber en uint32_t), lo cual es bastante legítimo.
Si bien funciona bien, no es muy limpio cuando desea distribuir una biblioteca con varios cientos de esas advertencias ...
¿Cómo lo hago correctamente?
¿Por qué hay un problema en primer lugar?
El proyecto en el que estoy trabajando consiste en definir la estructura de memoria de diferentes periféricos de toda una línea de microcontroladores (STMicroelectronics STM32). Para hacerlo, el resultado es una clase que contiene una unión de varias estructuras que definen todos los registros, dependiendo del microcontrolador objetivo.
Un ejemplo simple de un periférico bastante simple es el siguiente: una entrada / salida de uso general (GPIO)
union
{
struct
{
GPIO_MAP0_MODER;
GPIO_MAP0_OTYPER;
GPIO_MAP0_OSPEEDR;
GPIO_MAP0_PUPDR;
GPIO_MAP0_IDR;
GPIO_MAP0_ODR;
GPIO_MAP0_BSRR;
GPIO_MAP0_LCKR;
GPIO_MAP0_AFR;
GPIO_MAP0_BRR;
GPIO_MAP0_ASCR;
};
struct
{
GPIO_MAP1_CRL;
GPIO_MAP1_CRH;
GPIO_MAP1_IDR;
GPIO_MAP1_ODR;
GPIO_MAP1_BSRR;
GPIO_MAP1_BRR;
GPIO_MAP1_LCKR;
uint32_t :32;
GPIO_MAP1_AFRL;
GPIO_MAP1_AFRH;
uint32_t :64;
};
struct
{
uint32_t :192;
GPIO_MAP2_BSRRL;
GPIO_MAP2_BSRRH;
uint32_t :160;
};
};
Donde todo GPIO_MAPx_YYY
es una macro, definida como uint32_t :32
o el tipo de registro (una estructura dedicada).
Aquí ves el uint32_t :192;
que funciona bien, pero activa una advertencia.
Lo que he considerado hasta ahora:
Podría haberlo reemplazado por varios uint32_t :32;
(6 aquí), pero tengo algunos casos extremos en los que tengo uint32_t :1344;
(42) (entre otros). Por lo tanto, preferiría no agregar unas cien líneas sobre otras 8k, aunque la generación de la estructura esté programada.
El mensaje de advertencia exacto es algo como:
width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type
(Me encanta lo sombrío que es).
Preferiría no resolver esto simplemente eliminando la advertencia, sino usando
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-WTheRightFlag"
/* My code */
#pragma GCC diagnostic pop
puede ser una solución ... si la encuentro TheRightFlag
. Sin embargo, como se señala en este hilo , gcc/cp/class.c
con esta triste parte del código:
warning_at (DECL_SOURCE_LOCATION (field), 0,
"width of %qD exceeds its type", field);
Lo que nos dice que no hay una -Wxxx
bandera para eliminar esta advertencia ...
fuente
char unused[12];
y demás?uint32_t :192;
.:42*32
lugar de:1344
Respuestas:
Utilice varios campos de bits anónimos adyacentes. Entonces en lugar de:
por ejemplo, tendrías:
Uno por cada registro que desee mantener en el anonimato.
Si tiene espacios grandes para llenar, puede ser más claro y menos propenso a errores usar macros para repetir el espacio único de 32 bits. Por ejemplo, dado:
Luego, se puede agregar un espacio de 1344 (42 * 32 bits) así:
fuente
uint32_t :1344;
está en el lugar), así que preferiría no tener que ir por este camino ...¿Qué tal una forma de C ++?
Obtienes autocompletado debido al
GPIO
espacio de nombres y no hay necesidad de relleno ficticio. Incluso, está más claro lo que está sucediendo, ya que puede ver la dirección de cada registro, no tiene que depender en absoluto del comportamiento de relleno del compilador.fuente
ldr r0, [r4, #16]
, mientras que es más probable que los compiladores pierdan esa optimización con cada dirección declarada por separado. GCC probablemente cargará cada dirección GPIO en un registro separado. (De un grupo literal, aunque algunos de ellos se pueden representar como inmediatos rotados en codificación Thumb.)static volatile uint32_t &MAP0_MODER
, noinline
. Unainline
variable no se compila. (static
evita tener almacenamiento estático para el puntero, yvolatile
es exactamente lo que desea para MMIO para evitar la eliminación de almacenamiento muerto o la optimización de escritura / lectura)static
hace igualmente bien en este caso. Gracias por mencionarlovolatile
, lo agregaré a mi respuesta (y cambiaré en línea a estático, por lo que funciona para versiones anteriores de C ++ 17).GPIOA::togglePin()
.En el ámbito de los sistemas integrados, puede modelar hardware mediante el uso de una estructura o la definición de punteros a las direcciones de registro.
No se recomienda el modelado por estructura porque el compilador puede agregar relleno entre los miembros con fines de alineación (aunque muchos compiladores de sistemas integrados tienen un pragma para empaquetar la estructura).
Ejemplo:
También puede usar la notación de matriz:
Si debe usar la estructura, en mi humilde opinión, el mejor método para omitir direcciones sería definir un miembro y no acceder a él:
En uno de nuestros proyectos tenemos tanto constantes como estructuras de diferentes proveedores (el proveedor 1 usa constantes mientras que el proveedor 2 usa estructuras).
fuente
static
miembro de una estructura, asumiendo que la función de autocompletar puede mostrar miembros estáticos. De lo contrario, también pueden ser funciones miembro en línea.public:
yprivate:
tantas veces como desee para obtener el orden correcto de los campos).public
yprivate
no estáticos, entonces no es un tipo de diseño estándar , por lo que no proporciona las garantías de ordenación que está pensando. (Y estoy bastante seguro de que el caso de uso del OP requiere un tipo de diseño estándar).volatile
esas declaraciones, por cierto, para registros de E / S asignados en memoria.geza tiene razón en que realmente no quieres usar clases para esto.
Pero, si insistiera, la mejor manera de agregar un miembro no utilizado de n bytes de ancho es simplemente hacerlo:
Si agrega un pragma específico de implementación para evitar la adición de relleno arbitrario a los miembros de la clase, esto puede funcionar.
Para GNU C / C ++ (gcc, clang y otros que admiten las mismas extensiones), uno de los lugares válidos para poner el atributo es:
(ejemplo en el explorador del compilador Godbolt que muestra
offsetof(GPIO, b)
= 7 bytes).fuente
Para ampliar las respuestas de @ Clifford y @Adam Kotwasinski:
fuente
Para ampliar la respuesta de Clifford, siempre puede realizar una macro de los campos de bits anónimos.
Entonces en lugar de
utilizar
Y luego úsalo como
Desafortunadamente, necesitará tantas
EMPTY_32_X
variantes como bytes tenga :( Aún así, le permite tener declaraciones únicas en su estructura.fuente
#define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1
y#define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1
etc.Para definir un espaciador grande como grupos de 32 bits.
fuente
Creo que sería beneficioso introducir más estructura; lo que, a su vez, puede resolver el problema de los espaciadores.
Nombra las variantes
Si bien los espacios de nombres planos son agradables, el problema es que termina con una colección heterogénea de campos y no hay una forma sencilla de pasar todos los campos relacionados juntos. Además, al usar estructuras anónimas en una unión anónima, no puede pasar referencias a las estructuras en sí ni usarlas como parámetros de plantilla.
Como primer paso, consideraría, por lo tanto, romper el
struct
:Y finalmente, el encabezado global:
Ahora, puedo escribir un
void special_map0(Gpio:: Map0 volatile& map);
, así como obtener una descripción general rápida de todas las arquitecturas disponibles de un vistazo.Espaciadores simples
Con la definición dividida en varios encabezados, los encabezados son individualmente mucho más manejables.
Por lo tanto, mi enfoque inicial para cumplir exactamente con sus requisitos sería seguir repitiendo
std::uint32_t:32;
. Sí, agrega algunas líneas de centenas a las líneas de 8k existentes, pero dado que cada encabezado es individualmente más pequeño, puede que no sea tan malo.Sin embargo, si está dispuesto a considerar soluciones más exóticas ...
Presentamos $.
Es un hecho poco conocido que
$
es un carácter viable para los identificadores de C ++; incluso es un carácter inicial viable (a diferencia de los dígitos).Una
$
aparición en el código fuente probablemente$$$$
llamaría la atención y definitivamente llamará la atención durante las revisiones del código. Esto es algo que puede aprovechar fácilmente:Incluso puede armar un simple "lint" como un gancho de confirmación previa o en su CI que busca
$$$$
en el código C ++ comprometido y rechaza dichas confirmaciones.fuente
GPIO_MAP0_MODER
es de suponer que cada miembro me gustavolatile
.) Sin embargo, posiblemente el uso de referencia o parámetro de plantilla de los miembros previamente anónimos podría ser útil. Y para el caso general de estructuras de relleno, claro. Pero el caso de uso explica por qué el OP los dejó en el anonimato.$$$padding##Index_[N_];
para hacer que el nombre del campo sea más autoexplicativo en caso de que alguna vez aparezca en autocompletar o al depurar. (Ozz$$$padding
para ordenarlo después de losGPIO...
nombres, porque el objetivo de este ejercicio de acuerdo con el OP es autocompletar mejor para los nombres de ubicación de E / S mapeados en memoria)volatile
calificativo de la referencia, que se ha corregido. En cuanto a nombrar; Lo dejaré hasta el OP. Hay muchas variaciones (relleno, reservado, ...), e incluso el "mejor" prefijo para la finalización automática puede depender del IDE en cuestión, aunque aprecio la idea de modificar la clasificación.struct
) y queunion
terminan propagándose por todas partes incluso en bits específicos de la arquitectura que podría importarle menos los demás.Aunque acepto que las estructuras no deben usarse para el acceso al puerto de E / S de MCU, la pregunta original se puede responder de esta manera:
Es posible que deba reemplazar
__attribute__((packed))
con#pragma pack
o similar según la sintaxis de su compilador.La combinación de miembros públicos y privados en una estructura normalmente da como resultado que el diseño de la memoria ya no está garantizado por el estándar C ++. Sin embargo, si todos los miembros no estáticos de una estructura son privados, todavía se considera POD / diseño estándar, y también lo son las estructuras que los incrustan.
Por alguna razón, gcc produce una advertencia si un miembro de una estructura anónima es privado, así que tuve que darle un nombre. Alternativamente, envolverlo en otra estructura anónima también elimina la advertencia (esto puede ser un error).
Tenga en cuenta que el
spacer
miembro no es en sí mismo privado, por lo que aún se puede acceder a los datos de esta manera:Sin embargo, tal expresión parece un truco obvio y, con suerte, no se usaría sin una buena razón, y mucho menos como un error.
fuente
Anti-solución.
NO HAGA ESTO: Mezcle los campos público y privado.
¿Quizás sea útil una macro con un contador para generar nombres de variables únicos?
fuente