Cuando se programa en CI, me resulta invaluable empacar estructuras utilizando el __attribute__((__packed__))
atributo GCC para poder convertir fácilmente una porción estructurada de memoria volátil en una matriz de bytes que se transmitirán por un bus, se guardarán en el almacenamiento o se aplicarán a un bloque de registros. Las estructuras empaquetadas garantizan que, cuando se trata como una matriz de bytes, no contendrá ningún relleno, lo cual es un desperdicio, un posible riesgo de seguridad y posiblemente incompatible cuando se utiliza hardware de interfaz.
¿No hay un estándar para el embalaje de estructuras que funcione en todos los compiladores de C? Si no es así, ¿soy un caso atípico al pensar que esta es una característica crítica para la programación de sistemas? ¿Los primeros usuarios del lenguaje C no encontraron la necesidad de empacar estructuras o hay algún tipo de alternativa?
fuente
Respuestas:
En una estructura, lo que importa es el desplazamiento de cada miembro de la dirección de cada instancia de estructura. No se trata tanto de cuán apretadas están las cosas.
Una matriz, sin embargo, importa cómo se "empaqueta". La regla en C es que cada elemento de la matriz es exactamente N bytes del anterior, donde N es el número de bytes utilizados para almacenar ese tipo.
Pero con una estructura, no existe tal necesidad de uniformidad.
Aquí hay un ejemplo de un esquema de empaque extraño:
Freescale (que fabrica microcontroladores automotrices) crea un micro que tiene un coprocesador de unidad de procesamiento de tiempo (google para eTPU o TPU). Tiene dos tamaños de datos nativos, 8 bits y 24 bits, y solo se ocupa de enteros.
Esta estructura:
verá que cada U24 almacena su propio bloque de 32 bits, pero solo en el área de dirección más alta.
Esta:
tendrá dos U24s almacenada en adyacentes bloques de 32 bits, y la U8 será almacenada en el "agujero" en frente de la primera U24,
elementA
.Pero puede decirle al compilador que empaque todo en su propio bloque de 32 bits, si lo desea; es más costoso en RAM pero usa menos instrucciones para acceder.
"empaquetar" no significa "empacar herméticamente", solo significa algún esquema para organizar los elementos de una estructura con el desplazamiento.
No hay un esquema genérico, depende del compilador + arquitectura.
fuente
struct b
para moverseelementC
antes que cualquiera de los otros elementos, entonces no es un compilador de C conforme. El reordenamiento de elementos no está permitido en CComo mencionó
__attribute__((__packed__))
, supongo que su intención es eliminar todo el relleno dentro de astruct
(hacer que cada miembro tenga una alineación de 1 byte).... y la respuesta es no". El relleno y la alineación de datos en relación con una estructura (y matrices contiguas de estructuras en la pila o el montón) existen por una razón importante. En muchas máquinas, el acceso no alineado a la memoria puede conducir a una penalización de rendimiento potencialmente significativa (aunque disminuyendo en algunos hardware más nuevos). En algunos casos excepcionales, el acceso desalineado a la memoria conduce a un error del bus que es irrecuperable (incluso puede bloquear todo el sistema operativo).
Dado que el estándar C se enfoca en la portabilidad, tiene poco sentido tener una forma estándar de eliminar todo el relleno en una estructura y simplemente permitir que los campos arbitrarios estén desalineados, ya que hacerlo podría potencialmente hacer que el código C no sea portátil.
La forma más segura y portátil de enviar dichos datos a una fuente externa de una manera que elimine todo el relleno es serializar a / desde secuencias de bytes en lugar de simplemente tratar de enviar los contenidos de memoria sin procesar de su
structs
. Eso también evita que su programa sufra penalizaciones de rendimiento fuera de este contexto de serialización, y también le permitirá agregar libremente nuevos campos a unstruct
sin tirar y fallar todo el software. También le dará algo de espacio para abordar el endianness y cosas así si eso se convierte en una preocupación.Hay una forma de eliminar todo el relleno sin llegar a las directivas específicas del compilador, aunque solo es aplicable si el orden relativo entre campos no importa. Dado algo como esto:
... necesitamos el relleno para el acceso alineado a la memoria en relación con la dirección de la estructura que contiene estos campos, así:
... donde
.
indica relleno. Todosx
deben alinearse con un límite de 8 bytes para el rendimiento (y, a veces, incluso el comportamiento correcto).Puede eliminar el relleno de forma portátil utilizando una representación de SoA (estructura de matriz) como esta (supongamos que necesitamos 8
Foo
instancias):Efectivamente hemos demolido la estructura. En este caso, la representación de la memoria se convierte así:
... y esto:
... no más sobrecarga de relleno, y sin involucrar el acceso a la memoria desalineada ya que ya no estamos accediendo a estos campos de datos como un desplazamiento de una dirección de estructura, sino como un desplazamiento de una dirección base para lo que efectivamente es una matriz.
Esto también conlleva la ventaja de ser más rápido para el acceso secuencial como resultado de menos datos para consumir (no más relleno irrelevante en la mezcla para ralentizar la tasa de consumo de datos relevante de la máquina) y también un potencial para que el compilador vectorice el procesamiento de manera muy trivial .
La desventaja es que es un código PITA. También es potencialmente menos eficiente para el acceso aleatorio con el paso más grande entre campos, donde a menudo los representantes de AoS o AoSoA lo harán mejor. Pero esa es una forma estándar de eliminar el relleno y empacar las cosas lo más apretado posible sin atornillar con la alineación de todo.
fuente
No todas las arquitecturas son iguales, solo active la opción de 32 bits en un módulo y vea qué sucede cuando se usa el mismo código fuente y el mismo compilador. El orden de los bytes es otra limitación bien conocida. Agregue la representación de coma flotante y los problemas empeorarán. Usar el embalaje para enviar datos binarios no es portátil. Para estandarizarlo de modo que sea prácticamente utilizable, deberá redefinir la especificación del lenguaje C.
Aunque es común, usar Pack para enviar datos binarios es una mala idea si desea seguridad, portabilidad o longevidad de los datos. ¿Con qué frecuencia lees un blob binario de una fuente en tu programa? ¿Con qué frecuencia verificas que todos los valores son razonables, que un hacker o un cambio de programa no han "llegado" a los datos? Para cuando haya codificado una rutina de verificación, bien podría estar utilizando rutinas de importación y exportación.
fuente
Una alternativa muy común es el "relleno con nombre":
Esto hace suponer que la estructura no se rellenará a 8 bytes.
fuente