Cuando la programación en CI ha encontrado invaluable empacar estructuras usando GCC __attribute__((__packed__))
[...]
Como mencionó __attribute__((__packed__))
, supongo que su intención es eliminar todo el relleno dentro de a struct
(hacer que cada miembro tenga una alineación de 1 byte).
¿No hay un estándar para el embalaje de estructuras que funcione en todos los compiladores de C?
... 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 un struct
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:
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 alineado a la memoria en relación con la dirección de la estructura que contiene estos campos, así:
0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______y.......x_______y.......x_______y.......x_______y.......
... donde .
indica relleno. Todos x
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):
struct Foos
{
double x[8];
char y[8];
};
Efectivamente hemos demolido la estructura. En este caso, la representación de la memoria se convierte así:
0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______x_______x_______x_______x_______x_______x_______x_______
... y esto:
01234567
yyyyyyyy
... 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.