vigas planos metalicas leer lectura interpretar interpretacion estructuras estructurales estructura curso cuerpo como acero c networking portability strict-aliasing

planos - interpretacion de vigas



Forma correcta y portátil de interpretar el búfer como una estructura (2)

El contexto de mi problema está en la programación de la red. Supongamos que quiero enviar mensajes a través de la red entre dos programas. Para simplificar, digamos que los mensajes se ven así, y el orden de bytes no es una preocupación. Quiero encontrar una forma correcta, portátil y eficiente de definir estos mensajes como estructuras C. Conozco cuatro enfoques para esto: fundición explícita, transmisión a través de una unión, copia y clasificación.

struct message { uint16_t logical_id; uint16_t command; };

Lanzamiento explícito:

void send_message(struct message *msg) { uint8_t *bytes = (uint8_t *) msg; /* call to write/send/sendto here */ } void receive_message(uint8_t *bytes, size_t len) { assert(len >= sizeof(struct message); struct message *msg = (struct message*) bytes; /* And now use the message */ if (msg->command == SELF_DESTRUCT) /* ... */ }

send_message , send_message no infringe las reglas de alias, ya que un puntero de byte / char puede alias cualquier tipo. Sin embargo, lo contrario no es cierto, por lo que receive_message infringe las reglas de aliasing y, por lo tanto, tiene un comportamiento indefinido.

Casting a través de una Unión:

union message_u { struct message m; uint8_t bytes[sizeof(struct message)]; }; void receive_message_union(uint8_t *bytes, size_t len) { assert(len >= sizeof(struct message); union message_u *msgu = bytes; /* And now use the message */ if (msgu->m.command == SELF_DESTRUCT) /* ... */ }

Sin embargo, esto parece violar la idea de que una unión solo contiene uno de sus miembros en un momento dado. Además, parece que podría provocar problemas de alineación si el búfer de origen no está alineado en un límite de palabra o de media palabra.

Proceso de copiar:

void receive_message_copy(uint8_t *bytes, size_t len) { assert(len >= sizeof(struct message); struct message msg; memcpy(&msg, bytes, sizeof msg); /* And now use the message */ if (msg.command == SELF_DESTRUCT) /* ... */ }

Esto parece garantizar el resultado correcto, pero por supuesto preferiría no tener que copiar los datos.

Marshaling

void send_message(struct message *msg) { uint8_t bytes[4]; bytes[0] = msg.logical_id >> 8; bytes[1] = msg.logical_id & 0xff; bytes[2] = msg.command >> 8; bytes[3] = msg.command & 0xff; /* call to write/send/sendto here */ } void receive_message_marshal(uint8_t *bytes, size_t len) { /* No longer relying on the size of the struct being meaningful */ assert(len >= 4); struct message msg; msg.logical_id = (bytes[0] << 8) | bytes[1]; /* Big-endian */ msg.command = (bytes[2] << 8) | bytes[3]; /* And now use the message */ if (msg.command == SELF_DESTRUCT) /* ... */ }

Todavía tengo que copiar, pero ahora desacoplado de la representación de la estructura. Pero ahora necesitamos ser explícitos con la posición y el tamaño de cada miembro, y el endian-ness es un problema mucho más obvio.

Información relacionada:

¿Cuál es la regla de aliasing estricta?

Aliasing array con puntero-a-struct sin violar el estándar

¿Cuándo es seguro * para el alias de puntero estricto?

http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html

Ejemplo de mundo real

He estado buscando ejemplos de código de red para ver cómo se maneja esta situación en otro lado. La ip ligera tiene algunos casos similares. En el archivo udp.c encuentra el siguiente código:

/** * Process an incoming UDP datagram. * * Given an incoming UDP datagram (as a chain of pbufs) this function * finds a corresponding UDP PCB and hands over the pbuf to the pcbs * recv function. If no pcb is found or the datagram is incorrect, the * pbuf is freed. * * @param p pbuf to be demultiplexed to a UDP PCB (p->payload pointing to the UDP header) * @param inp network interface on which the datagram was received. * */ void udp_input(struct pbuf *p, struct netif *inp) { struct udp_hdr *udphdr; /* ... */ udphdr = (struct udp_hdr *)p->payload; /* ... */ }

donde struct udp_hdr es una representación empaquetada de un encabezado udp p->payload es de tipo void * . Según mi comprensión y this respuesta, definitivamente [edit- no] rompe el alias estricto y por lo tanto tiene un comportamiento indefinido.


La única forma correcta es, como supones, copiar los datos del búfer de caracteres en tu estructura. Sus otras alternativas infringen las reglas de alias estrictas o la regla de un miembro activo de la unión.

Quiero tomar un momento más para recordarte que incluso si haces esto en un único host y el orden de bytes no importa, aún tienes que asegurarte de que ambos extremos de la conexión se construyan con las mismas opciones y que la estructura está acolchado de la misma manera, los tipos son del mismo tamaño, etc. Sugiero tomar al menos una pequeña cantidad de tiempo considerando una implementación de serialización real, de modo que si alguna vez necesita soportar una variedad más amplia de condiciones, no tiene una gran actualización en frente de usted, entonces.


Supongo que esto es lo que he tratado de evitar, pero finalmente fui y eché un vistazo al estándar C99 . Esto es lo que he encontrado (énfasis agregado):
§6.3.2.2 vacío

1 El valor (inexistente) de una expresión nula (una expresión que tiene tipo nulo) no se utilizará de ninguna manera, y las conversiones implícitas o explícitas (excepto nulas) no se aplicarán a dicha expresión. Si una expresión de cualquier otro tipo se evalúa como una expresión vacía, su valor o designador se descarta. (Una expresión vacía se evalúa por sus efectos secundarios).

§6.3.2.3 Punteros

1 Un puntero a void se puede convertir desde o hacia un puntero a cualquier tipo incompleto o de objeto . Un puntero a cualquier tipo incompleto o de objeto se puede convertir a un puntero a vacío y viceversa; el resultado se comparará igual al puntero original.

Y §3.14

1 objeto
región de almacenamiento de datos en el entorno de ejecución, cuyos contenidos pueden representar valores

§6.5

Un objeto debe tener acceso a su valor almacenado solo mediante una expresión lvalue que tenga uno de los siguientes tipos:
- un tipo compatible con el tipo efectivo del objeto,
- una versión cualificada de un tipo compatible con el tipo de objeto efectivo,
- un tipo que es el tipo firmado o no firmado correspondiente al tipo efectivo del objeto,
- un tipo que es el tipo firmado o sin firmar correspondiente a una versión cualificada del tipo efectivo del objeto,
- un tipo agregado o de unión que incluye uno de los tipos mencionados entre sus
miembros (incluido, recursivamente, un miembro de un subaggregado o sindicato contenido), o
- un tipo de personaje.

§6.5

El tipo efectivo de un objeto para acceder a su valor almacenado es el tipo declarado de
objeto, si hay alguno. Si un valor se almacena en un objeto que no tiene un tipo declarado a través de un lvalue que tiene un tipo que no es un tipo de carácter, entonces el tipo de lvalue se convierte en el tipo efectivo del objeto para ese acceso y para los accesos subsiguientes que no modifican el valor almacenado . Si se copia un valor en un objeto que no tiene un tipo declarado utilizando memcpy o memmove, o se copia como una matriz de tipo de carácter, entonces el tipo efectivo del objeto modificado para ese acceso y para los accesos posteriores que no modifican el valor es el tipo efectivo del objeto desde el cual se copia el valor, si tiene uno. Para todos los demás accesos a un objeto que no tiene un tipo declarado, el tipo efectivo del objeto es simplemente el tipo de lvalue utilizado para el acceso.

§J.2 Comportamiento indefinido

- Se intenta usar el valor de una expresión vacía, o se aplica una conversión implícita o explícita (excepto a vacío) a una expresión vacía (6.3.2.2).

Conclusión

Está bien (bien definido) lanzar hacia y desde un void* , pero no está bien usar un valor de tipo void en C99 . Por lo tanto, el "ejemplo del mundo real" no es un comportamiento indefinido. Por lo tanto, el método de conversión explícita se puede usar con la siguiente modificación, siempre que se tenga en cuenta la alineación, el relleno y el orden de bytes:

void receive_message(void *bytes, size_t len) { assert(len >= sizeof(struct message); struct message *msg = (struct message*) bytes; /* And now use the message */ if (msg->command == SELF_DESTRUCT) /* ... */ }