Poprawny, przenośny sposób interpretowania bufora jako struktury

Kontekst mojego problemu dotyczy programowania w sieci. Powiedz, że chcę wysyłać wiadomości przez sieć między dwoma programami. Dla uproszczenia, powiedzmy, że wiadomości wyglądają tak, a kolejność bajtów nie jest problemem. Chcę znaleźć poprawny, przenośny i wydajny sposób definiowania tych wiadomości jako struktur C. Znam cztery podejścia do tego: wyraźne rzucanie, rzucanie przez związek, kopiowanie i zbieranie.

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

Moje rozumienie jest takiesend_message nie narusza reguł aliasingu, ponieważ wskaźnik bajtów / znaków może aliasować dowolny typ. Odwrotność nie jest jednak prawdziwareceive_message narusza zasady aliasingu i dlatego ma niezdefiniowane zachowanie.

Rzucanie przez Unię:
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)
        /* ... */
}

Wydaje się jednak, że narusza to ideę, że związek zawiera tylko jednego ze swoich członków w danym momencie. Ponadto wydaje się, że może to prowadzić do problemów z wyrównaniem, jeśli bufor źródłowy nie jest wyrównany na granicy słowa / pół-słowa.

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

Wydaje się to gwarantować poprawny wynik, ale oczywiście wolałbym nie kopiować danych.

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

Nadal musisz skopiować, ale teraz jest oddzielony od reprezentacji struktury. Ale teraz musimy jasno określić pozycję i rozmiar każdego członka, a endianizm jest bardziej oczywistym problemem.

Informacje pokrewne:

Jaka jest zasada ścisłego aliasingu?

Aliasing tablica ze wskaźnikiem do struktury bez naruszania standardu

Kiedy char * jest bezpieczny dla ścisłego aliasingu wskaźnika?

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

Przykład świata rzeczywistego

Szukałem przykładów kodu sieciowego, aby zobaczyć, jak ta sytuacja jest obsługiwana gdzie indziej. Thelekki ip ma kilka podobnych przypadków. wudp.c plik zawiera następujący kod:

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

  /* ... */
}

gdziestruct udp_hdr jest spakowaną reprezentacją nagłówka udp ip->payload jest typuvoid *. Idąc za moim zrozumieniem ito odpowiedz, to jestZdecydowanie [edycja - nie] łamanie aliasingu ścisłego, a zatem ma niezdefiniowane zachowanie.

questionAnswers(2)

yourAnswerToTheQuestion