Manera correcta, portátil para interpretar el búfer como una estructura.

El contexto de mi problema está en la programación en red. Digamos 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 manera correcta, portátil y eficiente de definir estos mensajes como C estructuras. Conozco cuatro enfoques para esto: el lanzamiento explícito, el lanzamiento a través de una unión, la copia y el cálculo de referencias.

struct message {
    uint16_t logical_id;
    uint16_t command;
};
Casting 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)
        /* ... */
}

Mi entendimiento es quesend_message no viola las reglas de aliasing, porque un puntero de byte / char puede alias cualquier tipo. Sin embargo, lo contrario no es cierto, y por lo tantoreceive_message viola las reglas de alias y por lo tanto tiene un comportamiento indefinido.

Casting Through a Union:
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 un sindicato solo contiene a uno de sus miembros en un momento dado. Además, esto parece que podría dar lugar a problemas de alineación si el búfer de origen no está alineado en un límite de palabra / 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 garantizado para producir 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 hay que copiar, pero ahora desacoplado de la representación de la estructura. Pero ahora debemos ser explícitos con la posición y el tamaño de cada miembro, y el carácter endémico es un tema mucho más obvio.

Información relacionada:

¿Cuál es la regla estricta de aliasing?

Aliasing array con puntero a estructura sin violar el estándar

¿Cuándo es seguro char * para un aliasing de puntero estricto?

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

Ejemplo del mundo real

He estado buscando ejemplos de código de red para ver cómo se maneja esta situación en otros lugares. losip ligero Tiene algunos casos similares. En eludp.c archivo se 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;

  /* ... */
}

dóndestruct udp_hdr es una representación empaquetada de un encabezado udp yp->payload es de tipovoid *. Siguiendo mi entendimiento yesta respuesta, esto esseguro [edit- not] rompe los alias estrictos y por lo tanto tiene un comportamiento indefinido.

Respuestas a la pregunta(2)

Su respuesta a la pregunta