Der Kontext meines Problems liegt in der Netzwerkprogrammierung. Angenommen, ich möchte Nachrichten zwischen zwei Programmen über das Netzwerk senden. Nehmen wir der Einfachheit halber an, dass Nachrichten so aussehen und die Byte-Reihenfolge keine Rolle spielt. Ich möchte einen korrekten, portablen und effizienten Weg finden, diese Nachrichten als C-Strukturen zu definieren. Ich kenne vier Ansätze dafür: 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 das send_message
verstößt nicht gegen Aliasing-Regeln, da ein Byte/Char-Zeiger jeden Typ aliasieren kann. Die Umkehrung gilt jedoch nicht, und so receive_message
verstößt gegen Aliasing-Regeln und hat daher 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 Idee 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/Halbwort-Grenze 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.
Rangieren
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)
/* ... */
}
Muss noch kopiert werden, aber jetzt von der Darstellung der Struktur entkoppelt. Aber jetzt müssen wir die Position und Größe jedes Mitglieds explizit angeben, und Endianness ist ein viel offensichtlicheres Problem.
Verwandte Informationen:
Was ist die strikte Aliasing-Regel?
Aliasing-Array mit Pointer-to-struct ohne Verletzung des Standards
Wann ist char* sicher für striktes Pointer-Aliasing?
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 anderswo gehandhabt wird. Das leichtes ip hat ein paar ähnliche Fälle. In dem udp.c Datei liegt folgender 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;
/* ... */
}
wo struct udp_hdr
ist eine gepackte Darstellung eines UDP-Headers und p->payload
ist vom Typ void *
. Nach meinem Verständnis und dieser Antwort ist dies bestimmt [edit- not] bricht das strikte Aliasing und hat daher ein undefiniertes Verhalten.
Ich nehme an, der korrekteste (unkorrekt beabsichtigte) Weg wäre, eine neue Struktur zuzuweisen, die Werte separat zu lesen und sie einzeln auszufüllen.
– Kninnug
3. Oktober 2013 um 17:13 Uhr
@Kninnug ja, aber ich suche auch Effizienz & Eleganz. Ich hätte kein Problem damit, es für ein einfaches Beispiel wie dieses manuell zu tun, aber wenn es mehr Felder und nicht übereinstimmende Byte-Reihenfolgen gibt, wird es schnell hässlich.
– Croyd
3. Oktober 2013 um 17:17 Uhr
Da es Unterschiede in Endianness, Polsterung usw. geben könnte, wird es keinen glatten, einzeiligen, eleganten Weg geben, dies zu tun. Ich denke, @Kninnug hat recht damit, welcher Ansatz am robustesten wäre. Sie könnten eine Vereinigung oder eine Speicherkopie erstellen, aber dann müssen Sie einige starke Annahmen darüber treffen, was unter der Haube passiert, und es wird noch weniger portabel sein.
– Schleicher
3. Oktober 2013 um 17:20 Uhr
Bitte geben Sie getrennte Fragen für C und C++ ein und entfernen Sie eines der Tags aus dieser Frage. Die Antworten sind für die verschiedenen Sprachen unterschiedlich. Beispielsweise erlaubt C 1999 den Zugriff auf ein anderes Union-Member als das zuletzt gespeicherte (die Bytes werden als neuer Typ neu interpretiert), C++ jedoch nicht.
– Eric Postpischil
3. Oktober 2013 um 17:24 Uhr
Wenn Sie Probleme mit Endianness, Elementgröße, Polsterung und Ausrichtung nicht berücksichtigen, wäre es schwierig, das Ergebnis als “tragbar” zu bezeichnen.
– Markieren Sie Lösegeld
3. Oktober 2013 um 17:47 Uhr