¿El __attribute__((__packed__)) de GCC conserva el orden original?

8 minutos de lectura

Propósito

Estoy escribiendo un programa de red en C (específicamente gnu89) y me gustaría simplificar las cosas reinterpretando un cierto struct X como una gran variedad de bytes (también conocido como char), enviando los bytes a través de la red y reinterpretándolos como struct X Por otro lado. Con este fin he decidido usar el __attribute__((__packed__ )) de gcc. He hecho todo lo posible para asegurarme de que esto se haga correctamente (es decir, he tenido en cuenta el endianness y otros problemas relacionados).

Pregunta

Aparte de garantizar que struct X es lo más pequeño posible, gcc garantiza que un struct definido con __attribute__((__packed__ )) conserva el orden original? He buscado bastante y aún no he encontrado ninguna documentación sobre si existe o no esta garantía.

notas

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

  • ¿Qué quiere decir con “pedido original”? ¿Quiere decir que los miembros de la estructura están literalmente en el mismo orden que se especifica en la definición?

    – Roberto Gamble

    18 de noviembre de 2009 a las 15:40

  • Dado que está escribiendo un programa de red, es probable que se dirija a un mundo de problemas cuando sus datos van a una máquina que utiliza un chip SPARC o PowerPC, por nombrar solo dos familias, cuando la fuente es un chip Intel. Además, si hace algo más que cargar y enviar los datos con las estructuras empaquetadas, el impacto en el rendimiento al acceder a los datos empaquetados probablemente sea mucho peor que si escribe el código para serializar los datos de una manera neutral a la plataforma a partir de estructuras de datos desempaquetadas. Además, las estructuras de datos bien diseñadas no tienen agujeros aleatorios; es posible hacerlo.

    –Jonathan Leffler

    18 de noviembre de 2009 a las 15:58

  • +1 al comentario de Jonathan arriba. Rediseñe su estructura para que no tenga agujeros en la primera línea. (El elemento más grande al más pequeño generalmente debería ser suficiente).

    – DevSolar

    18 de noviembre de 2009 a las 16:02

  • @Jonathan Leffler Escribo este programa solo para uso personal. Conozco de antemano las arquitecturas de las máquinas que se conectan.

    luego

    19 de noviembre de 2009 a las 17:58

avatar de usuario
Ambroz Bizjak

Sí, __attribute__((packed)) (sin necesidad de 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, debes entender que packed no solo empaqueta la estructura, sino también:

  • hace que su alineación requerida sea de 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 estructura misma, se leen y escriben correctamente, es decir el desalineamiento de sus campos es tratado en el software por el compilador.

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

#include <stdio.h>
#include <inttypes.h>
#include <arpa/inet.h>

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 un resultado aparentemente incorrecto

y=2, *py=1

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 que no son campos de bits y las unidades en las que residen los campos de bits tienen direcciones que aumentan en el orden en que se declaran.

y la documentación del atributo empaquetado establece claramente que solo se ve afectado el relleno/alineación:

El atributo empaquetado especifica que un campo de estructura o variable 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.

  • La cita del documento GCC no me aclara si el campo de bit ordenando es mantenido.

    – Ciro Santilli Путлер Капут 六四事

    30 de julio de 2017 a las 11:07


Si.

Sin embargo, usando __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 compiladores a menudo implementan extensiones gcc, el uso de esta extensión específica del compilador significa que no tiene un programa C conforme. Esto significa que si otro compilador o incluso un futuro gcc cambia lleno o no lo implementa en absoluto, no tiene suerte y ni siquiera puede quejarse con nadie. Gcc podría dejarlo mañana y seguir siendo un compilador C99. (Ok, exactamente eso es poco probable.) La mayoría de nosotros tratamos de escribir programas conformes no porque tengamos una hostilidad abstracta hacia el uso de software de proveedores o deseemos algún estándar académico de pureza de 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 que nuestro código haga lo correcto día a día y de sistema a sistema si lo hacemos de esa manera.
  • Estás reinventando la rueda; este problema ya se ha resuelto de forma estándar: consulte YAML, XML, y JSON.
  • Si se trata de un protocolo de tan bajo nivel que YAML, XML y JSON no están disponibles, realmente debería tomar tipos fundamentales individuales, aplicar la versión de su host de hton?() y ntoh?(), y memcpy() a una salida buffer. Me doy cuenta de que existe una larga tradición de leer y escribir directamente desde estructuras, pero también pasé mucho tiempo arreglando ese código más tarde cuando se movió de entornos de 32 bits a 64 bits…

  • @DigitalRoss Soy el primero en escribir código que cumple estrictamente con ISO C90. Nunca uso hacks específicos del compilador. Sin embargo, en este caso estoy escribiendo código completamente para uso personal.

    luego

    19 de noviembre de 2009 a las 17:54

  • Bueno, estoy de acuerdo en que mi respuesta no estaba muy bien dirigida a tu pregunta, supongo que la eliminaré…

    – DigitalRoss

    19 de noviembre de 2009 a las 19:07

  • @DigitalRoss ¡No elimine la respuesta! Puede ayudar a alguien más que viene a este hilo.

    luego

    20 de noviembre de 2009 a las 0:45

  • Si el OP solo se preocupa por el “uso personal” y tiene máquinas con el mismo orden de bytes en ambos lados del enlace, entonces es casi seguro que ambos lados también tengan las mismas restricciones de alineación. Si bien la alineación está definida por la implementación, eso no significa que sea algo que cambiará debajo de usted en cada revisión del compilador. Preservar el comportamiento de alineación es esencial para todos, excepto para el uso más trivial del código de la biblioteca, y gcc en x86 incluso excluye la alineación óptima de double en aras de mantener la compatibilidad con la alineación ABI existente.

    – R.. GitHub DEJA DE AYUDAR A ICE

    11 de agosto de 2010 a las 7:08

Según lo que intenta hacer, le recomiendo encarecidamente que también use 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 evitará que tenga que hacer cosas como las siguientes:

struct name
{
    short field : 8;
};

Usamos esta técnica con frecuencia para convertir mensajes entre una matriz de bytes y una estructura, y nunca hemos tenido problemas con ella. Es posible que deba realizar la conversión endianness usted mismo, pero el orden de los campos no es un problema. Si tiene alguna inquietud sobre los tamaños 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 someField se almacenará como 16 bits y no se reorganizará ni modificará para ajustarse a los límites de bytes.

avatar de usuario
Adán bueno

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

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad