¿Existe una forma estándar o alternativa estándar para empaquetar una estructura en c?

12

Cuando la programación en CI ha resultado inestimable para las estructuras de paquetes que utilizan el atributo GCCs __attribute__((__packed__)) , puedo convertir fácilmente un trozo estructurado de memoria volátil en una matriz de bytes para ser transmitidos a través de un bus, guardados en el almacenamiento o aplicados a un bloque de registros. Las estructuras empaquetadas garantizan que, cuando se trata como una matriz de bytes, no contendrá ningún relleno, lo que es un desperdicio, un posible riesgo de seguridad y posiblemente incompatible con el hardware de interfaz.

¿No hay un estándar para empaquetar estructuras que funcione en todos los compiladores de C? Si no es así, ¿soy un valor atípico al pensar que esta es una característica crítica para la programación de sistemas? ¿Los usuarios tempranos del lenguaje C no encontraron la necesidad de empaquetar estructuras o hay algún tipo de alternativa?

    
pregunta satur9nine 12.01.2016 - 22:17

4 respuestas

12

En una estructura, lo que importa es el desplazamiento de cada miembro de la dirección de cada instancia de estructura. No es tanto la cuestión de cuán apretadas están las cosas.

Sin embargo, una matriz importa en cómo está "empaquetada". La regla en C es que cada elemento de la matriz es exactamente N bytes de la anterior, donde N es el número de bytes utilizados para almacenar ese tipo.

Pero con una estructura, no hay tal necesidad de uniformidad.

Aquí hay un ejemplo de un esquema de embalaje extraño:

Freescale (que fabrica microcontroladores para automóviles) hace un micro que tiene un coprocesador de la unidad de procesamiento de tiempo (google para eTPU o TPU). Tiene dos tamaños de datos nativos, 8 bits y 24 bits, y solo trata con enteros.

Esta estructura:

struct a
{
  U24 elementA;
  U24 elementB;
};

verá que cada U24 almacenó su propio bloque de 32 bits, pero solo en el área de dirección más alta.

Esto:

struct b
{
  U24 elementA;
  U24 elementB;
  U8  elementC;
};

tendrá dos U24 almacenados en bloques adyacentes de 32 bits, y el U8 se almacenará en el "agujero" frente al primer U24, elementA .

Pero puede decirle al compilador que incluya todo en su propio bloque de 32 bits, si lo desea; es más costoso en RAM pero usa menos instrucciones para accesos.

"empaquetar" no significa "empaquetar apretadamente", solo significa un esquema para organizar los elementos de una estructura contra el desplazamiento.

No hay un esquema genérico, depende del compilador + de la arquitectura.

    
respondido por el RichColours 12.01.2016 - 23:50
6
  

Al programar en C, he encontrado que es invaluable empacar estructuras usando   GCCs __attribute__((__packed__)) [...]

Dado que menciona __attribute__((__packed__)) , asumo que su intención es eliminar todo el relleno dentro de un struct (para que cada miembro tenga una alineación de 1 byte).

  

¿No hay ningún estándar para empaquetar estructuras que funcione en todas las C?   compiladores?

... y la respuesta es "no". El relleno y la alineación de los datos en relación con una estructura (y matrices contiguas de estructuras en la pila o pila) existen por una razón importante. En muchas máquinas, el acceso a la memoria no alineada puede llevar a una penalización de rendimiento potencialmente importante (aunque se reduce en algunos equipos más nuevos). En algunos casos raros, el acceso a la memoria mal alineado provoca un error de bus que no se puede recuperar (incluso puede bloquear el sistema operativo completo).

Dado que el estándar C está enfocado en la portabilidad, tiene poco sentido tener una manera estándar de eliminar todo el relleno en una estructura y simplemente permitir que 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 flujos de bytes en lugar de 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 un struct sin desechar y fallar todo el software. También te dará algo de espacio para enfrentar el endianismo y cosas como esas si alguna vez 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 los campos no importa. Dado algo como esto:

struct Foo
{
    double x;  // assume 8-byte alignment
    char y;    // assume 1-byte alignment
               // 7 bytes of padding for first field
};

... necesitamos el relleno para el acceso a la memoria alineado en relación con la dirección de la estructura que contiene estos campos, como:

0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______y.......x_______y.......x_______y.......x_______y.......

... donde . indica el relleno. Cada x debe 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 tal (supongamos que necesitamos 8% de instancias de Foo ):

struct Foos
{
   double x[8];
   char y[8];
};

Hemos demolido efectivamente la estructura. En este caso, la representación de la memoria se vuelve así:

0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______x_______x_______x_______x_______x_______x_______x_______

... y esto:

01234567
yyyyyyyy

... no hay más sobrecarga de relleno, y sin implicar un acceso de memoria mal alineado 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 es efectivamente una matriz .

Esto también conlleva la ventaja de ser más rápido para el acceso secuencial como resultado de la menor cantidad de datos para consumir (no hay más relleno irrelevante en la mezcla para reducir la tasa de consumo de datos relevante de la máquina) y también un potencial para que el compilador se vectorice Procesando muy trivialmente.

El inconveniente es que es un PITA para codificar. También es potencialmente menos eficiente para el acceso aleatorio con el paso más amplio 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 apretadamente posible sin atornillar la alineación de todo.

    
respondido por el user204677 14.01.2016 - 21:39
5

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 bytes es otra limitación bien conocida. El tiro en la representación de punto flotante y los problemas empeoran. El uso de Embalaje para enviar datos binarios no es portátil. Para estandarizarlo de modo que fuera prácticamente utilizable, tendría que 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 comprueba que todos los valores son sensatos, que un pirata informático o un cambio de programa no ha "conseguido" los datos? En el momento en que haya codificado una rutina de verificación, también podría estar usando rutinas de importación y exportación.

    
respondido por el mattnz 13.01.2016 - 08:18
0

Una alternativa muy común es el "relleno denominado":

struct s {
  short s1;
  char  c2;
  char  reserved; // Padding
};

Este hace supone que la estructura no se rellenará a 8 bytes.

    
respondido por el MSalters 13.01.2016 - 10:33

Lea otras preguntas en las etiquetas