Korrekte, portable Methode, um Puffer als Struktur zu interpretieren

Der Kontext meines Problems liegt in der Netzwerkprogrammierung. Angenommen, ich möchte Nachrichten über das Netzwerk zwischen zwei Programmen senden. Nehmen wir der Einfachheit halber an, die Nachrichten sehen so aus, und die Bytereihenfolge spielt keine Rolle. Ich möchte einen korrekten, portablen und effizienten Weg finden, um diese Nachrichten als C-Strukturen zu definieren. Ich kenne dazu vier Ansätze: explizites Casting, Casting durch eine Union, Kopieren und Marshalling.

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

Mein Verständnis ist dassend_message Verstößt nicht gegen Aliasing-Regeln, da ein Byte- / Zeichenzeiger einen beliebigen Typ als Alias ​​verwenden kann. Das Gegenteil ist jedoch nicht der Fall und soreceive_message Verstößt gegen Aliasing-Regeln und hat somit undefiniertes Verhalten.

Casting durch eine 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)
        /* ... */
}

Dies scheint jedoch gegen die Vorstellung zu verstoßen, dass eine Gewerkschaft zu einem bestimmten Zeitpunkt nur eines ihrer Mitglieder enthält. Darüber hinaus scheint dies zu Ausrichtungsproblemen führen zu können, wenn der Quellpuffer nicht an einer Wort- / Halbwortgrenze ausgerichtet ist.

Kopieren:
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)
        /* ... */
}

Dies scheint garantiert das richtige Ergebnis zu liefern, aber natürlich würde ich es vorziehen, die Daten nicht kopieren zu müssen.

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)
        /* ... */
}

Müssen noch kopieren, aber jetzt von der Darstellung der Struktur entkoppelt. Jetzt müssen wir jedoch die Position und Größe der einzelnen Mitglieder genau angeben, und die Endianität ist ein viel offensichtlicheres Problem.

Verwandte Informationen:

Was ist die strenge Aliasing-Regel?

Aliasing-Array mit Pointer-to-Struct ohne Verletzung des Standards

Wann ist char * für striktes Pointer-Aliasing sicher?

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

Beispiel aus der realen Welt

Ich habe nach Beispielen für Netzwerkcode gesucht, um zu sehen, wie diese Situation an anderer Stelle gehandhabt wird. Dasleichte ip hat ein paar ähnliche Fälle. In demudp.c Datei liegt der folgende Code:

/**
 * 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;

  /* ... */
}

woherstruct udp_hdr ist eine gepackte Darstellung eines udp-Headers undp->payload ist vom Typvoid *. Ich gehe auf mein Verständnis unddiese Antwort, das istbestimmt [edit-not] unterbricht das strikte Aliasing und hat somit ein undefiniertes Verhalten.

Antworten auf die Frage(2)

Ihre Antwort auf die Frage