C++ Struct serialisieren nötig?

T_55

Lieutenant
Registriert
Feb. 2013
Beiträge
638
Hallo,

ich will mich mal an Boost.Asio herantasten und ein Struct per Netzwerk verschicken. In Beispielen habe ich gesehen das Strukturen serialisiert wurden.
Meine Frage: Müssen Structs serialisiert werden oder muss dies nicht sein?
Und hätte die eine oder andere Variante Einfluss auf die "Sicherheit" also Thema buffer overflow?

Gruß
 
So oder so muss dein Objekt in irgendeiner Form serialisiert werden, wenn du es auf ein anderes System übertragen willst.
Ein struct besteht ja aus einer Reihe von Feldern, also mehr oder weniger einem Speicherbereich mit Startadresse und Länge, wobei die Felder beliebig komplex aussehen können. Das kannst du ja schlecht verschicken, da auf dem Zielsystem das Objekt nicht existiert und die Adresse(n) nicht zwingend verfügbar sind. Folglich musst du das Objekt serialisieren, verschicken und im Zielsystem deserialisieren (sofern du es verwenden möchtest).

Prinzipiell kann man nach folgender Faustregel vorgehen:
Ist es ein atomarer Datentyp oder ein Array von atomaren Datentypen? Dann kannst du es problemlos roh verschicken (im Rahmen der Bibliotheksfunktionen versteht sich).
Ist es kein atomarer Datentyp und kein Array von atomaren Datentypen, dann muss es vorher serialisiert werden.

Es gibt natürlich Ausnahmen in beide Richtungen, aber als Faustregel hilft das schonmal.
 
Jein. Hängt davon ab was dein struct ist.

Also in ANSI C brauchst du nur ein pointer auf struct variable und struct länge (sizeof), dann kannst es auch gleich über winsock, boost oder was auch immer schicken und beim empfänger den empfangenen buffer wieder zu struct casten. Allerdings vorsicht: enthält dein struct pointer, sind diese im empfänger-prozess natürlich nicht mehr gültig. Versuchst du sie zu dereferenzieren hast du Speicherzugriffsverletzung provoziert. In diesem fall muss serialisiert werden.

In C++, wenn referenzen und konstruktoren im spiel sind, gibt es praktisch keine andere Wahl als zu serialisieren.
Schau dir einfach Boost::serialization an.

Serialisierung/deserialisierung ist natürlich zusätzlicher Code, mehr Komplexität = größere Angriffsfläche für Probleme.
Solange du weißt was du tust ist das Risiko vertretbar.
 
Zuletzt bearbeitet:
Folgende Probleme können entstehen, wenn ein Programm einfach eine Struct byte für byte über das Netzwerk kopiert:
- Zeiger verlieren ihre Gültigkeit
- Je nach System können die Datentypen anders sein (CPP schreibt ja keine Fixe größe für die diversen Datentypen vor; zudem gibt es je nach System potentiell unterschiedliche Endianness; auch gibt es potentielle Probleme bei 64 bit vs 32 bit wegen der unterschiedlichen Pointer size).
- Je nach System kann das Padding und Alignement innerhalb der Struct anders sein (https://en.wikipedia.org/wiki/Data_structure_alignment)

Gerade die letzten beiden Punkte führen dann dazu, dass die Struct auf zwei Systemen ein unterschiedliches Speicherlayout hat. Dementsprechend interpretiert der Empfänger dann die Daten anders als der Sende. Eben diese potentiellen Probleme lassen sich durch die Serialisierung vermeiden.
 
Zuletzt bearbeitet:
Nai schrieb:
Zusätzlich können die System verschiedene endianness verwenden.

Allerdings gibt es eine Bibliothek, die deine Idee aufgreift:
cap'n'proto verwendet Klassen, die bei ihren Zugriffen ein portables Speicherlayout verwenden und dadurch ohne zusätzliche Serialisierung verschickt werden können. Das kommt allerdings mit einem kleinen Aufpreis bei regulären Zugriffen, den hast du aber bei anderen Serialisierungsvarianten auch.
 
Guter punkt mit endianess, das habe ich ganz vergessen.

Wobei man in den modernen compilern alignment und sogar endianess der structs festnageln kann.
Padding ist bei der serialisierung/deserialisierung eigentlich unwichtig (ist nur ein Mittel um alignment zu erreichen bzw alignment führt zu padding).
 
Zuletzt bearbeitet:
Öfters muss man auf Zielseite die reinkommenden Daten auch auf Plausibiltät prüfen. Unterwegs kann ja was verunfallt oder verunfallt worden sein.
 
Das kommt auf das übertragende Protokoll bzw auf die verwendete Kommunikationsbibliothek an. Bei TCP als Protokoll ist zum Beispiel eine solche Überprüfung überflüssig, da TCP die Überprüfung der "Korrektheit" (Reihenfolge, keine verlorenen Pakete, keine Fehler im Inhalt) selbst übernimmt. Bei UDP werden nur keine Fehler im Inhalt garantiert, die Reihenfolge der Pakete kann aber beliebig sein und zudem können Pakete verloren gehen.
 
Aber man müsste wahrscheinlich schon prüfen ob zB bei einem puren Array bzw ein Vektor nicht viel größer ankommt als der Speicher beim Ziel vorgesehen ist oder erledigt das TCP auch?

Bei Structs könnte man durch pragma pack ein exaktes Fitting (1) also evt schon eine Gleichartigkeit bei Sender und Empfänger herstellen aber so gut kenne ich mich da auch nicht aus.

Auf der anderen Seite ist die Flexibilität durch zB boost serialization natürlich gut denn dieses kann wohl mit Pointern und Referenzen gut umgehen und liefert die Inhalte der Adressen.
 
C++11 Standard schrieb:
A preprocessing directive of the form
# pragma pp-tokensopt new-line
causes the implementation to behave in an implementation-defined manner. The behavior might cause
translation to fail or cause the translator or the resulting program to behave in a non-conforming manner.
Any pragma that is not recognized by the implementation is ignored

Pragmas im Allgemeinen sind ein Bestandteil des C++ Standards. Was ein konkretes Pragma macht ist allerdings compilerabhängig. Wie im obigen Zitat lesbar sollten pragmas die für einen Compiler nicht implementiert sind von diesem ignoriert werden.


Mit den POD structs hast du leider nicht Recht. Diese haben kein garantiertes Speicherlayout. Ein Compiler kann da Padding reinhauen wie er lustig ist.

C++11 Standard schrieb:
A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its
initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa. [ Note:
There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning,
as necessary to achieve appropriate alignment. — end note ]
POD structs werden da zwar nicht explizit erwähnt, aber da im gesamten Standard an keinem Punkt erwähnt wird, dass PODs kein Padding haben dürfen ist es auch nicht vorgeschrieben. Abgesehen davon wird in einer älteren Version (9.2 18) des Standards anstatt "standard-layout struct" "POD-struct" verwendet.

Aus diesem Grund überhaupt gibt es das pack pragma überhaupt und wird von sehr vielen Compilern implementiert. Meines Wissens gibt es bis heute keine bessere Alternative, aber ich lasse mich da gerne etwas besseren belehren.
 
Ich habe meinen Beitrag mal entfernt. Ich weiß, dass Padding angewedet wird, ich war allerdings davon ausgegangen, dass es feste Regeln dafür gibt, nämlich dass der Compiler immer genau zum Alignmeht des jeweiligen Typen padden muss und nicht mehr oder weniger als das. Aber das scheint nicht zu stimmen.
 
Zurück
Oben