¿El __ atributo __ (__ paquete__) de GCC) conserva el orden original?

Propósito

Estoy escribiendo un progtwig de red en C (específicamente gnu89 ) y me gustaría simplificar las cosas reinterpretando una cierta struct X como una gran matriz de bytes (aka char ), enviando los bytes a través de la red y reinterpretándolos como struct X en el otro lado. Para este fin, he decidido usar __attribute __ ((__ packed__) de gcc). Hice todo lo posible para asegurarme de que esto se haga correctamente (es decir, he tenido en cuenta el endianismo y otros problemas relacionados).

Pregunta

Además de garantizar que struct X sea ​​lo más pequeña posible, ¿gcc garantiza que una struct definida con __attribute __ ((__ packed__)) conserva el orden original? He realizado una buena búsqueda y todavía no he encontrado ninguna documentación sobre si existe o no esta garantía.

Notas

Es seguro suponer que tanto el emisor como el receptor no encontrarán problemas de portabilidad (por ejemplo, sizeof(int) en el servidor es igual a sizeof(int) en el cliente).

Suponiendo que está preguntando si los miembros de la estructura conservarán el orden especificado en su definición, la respuesta es sí. El Estándar requiere que los miembros sucesivos tengan direcciones crecientes:

Sección §6.7.2.1p13:

Dentro de un objeto de estructura, los miembros de campo que no son de bit y las unidades en que residen los campos de bit tienen direcciones que aumentan en el orden en que se declaran.

y la documentación para el atributo empaquetado establece claramente que solo se afecta el relleno / alineación:

El atributo empaquetado especifica que una variable o campo de estructura debe tener la alineación más pequeña posible: un byte para una variable y un bit para un campo, a menos que especifique un valor mayor con el atributo alineado.

Sí, __attribute__((packed)) (no es necesario un segundo conjunto de guiones bajos) es una forma correcta de implementar protocolos de red binarios (es decir, sin texto). No habrá espacios entre los elementos.

Sin embargo, debe comprender que el packed no solo empaqueta la estructura, sino también:

  • hace su alineación requerida un byte, y
  • se asegura de que sus miembros, que pueden desalinearse por el empaque y por la falta de alineación requerida por la propia estructura, se leen y escriben correctamente, es decir, la desalineación de sus campos es tratada en el software por el comstackdor .

Sin embargo , el comstackdor solo se ocupará de la desalineación si accede directamente a los miembros de la estructura. Nunca debe hacer un puntero a un miembro de una estructura empaquetada (excepto cuando sepa que la alineación requerida del miembro es 1, como char u otra estructura empaquetada). El siguiente código C demuestra el problema:

 #include  #include  #include  struct packet { uint8_t x; uint32_t y; } __attribute__((packed)); int main () { uint8_t bytes[5] = {1, 0, 0, 0, 2}; struct packet *p = (struct packet *)bytes; // compiler handles misalignment because it knows that // "struct packet" is packed printf("y=%"PRIX32", ", ntohl(p->y)); // compiler does not handle misalignment - py does not inherit // the packed attribute uint32_t *py = &p->y; printf("*py=%"PRIX32"\n", ntohl(*py)); return 0; } 

En un sistema x86 (que no impone la alineación de acceso a la memoria), esto producirá

 y=2, *py=2 

como se esperaba. Por otro lado en mi placa ARM Linux, por ejemplo, produjo el resultado aparentemente incorrecto

 y=2, *py=1 

Usamos esta técnica con frecuencia para convertir mensajes entre una matriz de bytes y una estructura, y nunca hemos tenido problemas con ella. Puede que tenga que realizar la conversión de endianness usted mismo, pero el orden de campo no es un problema. Si le preocupa el tamaño de los tipos de datos, siempre puede especificar el tamaño del campo de la siguiente manera:

 struct foo { short someField : 16 __attribute__ ((packed)); }; 

Esto garantiza que algunos campos se almacenarán en 16 bits y no se reorganizarán ni modificarán para ajustarse a los límites de bytes.

Sí.

Sin embargo, usar __attribute__((__packed__)) no es una buena manera de hacer lo que estás haciendo.

  • No resuelve problemas de orden de bytes
  • El acceso a la estructura será lento
  • Aunque la popularidad de gcc ha llevado a una situación en la que otros comstackdores a menudo implementan extensiones gcc, el uso de esta extensión específica del comstackdor significa que no tiene un progtwig C conforme. Esto significa que si otro comstackdor o incluso un futuro gcc cambia empaquetado o no lo implementa en absoluto, no tiene suerte y ni siquiera puede quejarse a nadie. Gcc podría descartarlo mañana y seguir siendo un comstackdor C99. (Ok, esa cosa exacta es improbable). La mayoría de nosotros intenta escribir progtwigs conformes no porque tengamos una hostilidad abstracta al uso de software de proveedores o deseemos algún estándar académico de pureza del código, sino porque sabemos que solo las características del lenguaje conforme tienen un especificación precisa y común, por lo que es mucho más fácil depender de nuestro código haciendo lo correcto día a día y sistema a sistema si lo hacemos de esa manera.
  • Estás reinventando la rueda; este problema ya ha sido resuelto de forma estándar: ver YAML , XML y JSON .
  • Si es un protocolo de tan bajo nivel que YAML, XML y JSON no están disponibles, debería tomar tipos fundamentales individuales, aplicar la versión de host de hton? () Y ntoh? (), Y memcpy () a una salida buffer. Me doy cuenta de que hay una larga tradición de lectura y escritura directamente desde las estructuras, pero también he dedicado mucho tiempo a corregir ese código más adelante cuando se movió de entornos de 32 bits a 64 bits …

En función de lo que intenta hacer, le recomiendo encarecidamente que también utilice tipos de datos de tamaño fijo (es decir, uint32_t, int16_t, etc.) que se encuentran en stdint.h. El uso de tipos de datos de tamaño fijo le evitará tener que hacer cosas como las siguientes:

 struct name { short field : 8; }; 

Sí, C tiene la garantía de que los elementos de la estructura no se reordenarán. (Puede haber extensiones o sistemas de optimización sofisticados que podrían cambiar esto, pero no de forma predeterminada en gcc).