Hallo zusammen, im Forum kommen öfters Beiträge mit Fragen und Unklarheiten oder gar Falschaussagen darüber, wie Daten in C(++) zur Übertragung in Bytes umgewandelt, d.h. serialisiert, werden können, wie z.B.: Beitrag "unaligned acces bei einem imx6q" Beitrag "union mit Struktur und Array gleicher Größe?" Beitrag "Re: Ansi C: Convertierung int to string" Um zukünftige Diskussionen abzukürzen habe ich die diversen Möglichkeiten in einem Artikel zusammengefasst: Serialisierung. Außerdem habe ich den bereits hier ( Beitrag "Re: Endianess beim Kopieren automatisieren" ) gezeigten Ansatz erweitert und eine komplette C++-Bibliothek implementiert, welche die portable und standardkonforme Umwandlung von Datenstrukturen in Binärdaten ermöglicht: * https://github.com/Erlkoenig90/uSer Projektseite * https://erlkoenig90.github.io/uSer-doc/html/index.html Online-Dokumentation * https://erlkoenig90.github.io/uSer-doc/html/tutorial.html Tutorial * https://erlkoenig90.github.io/uSer-doc/html/Examples.html Beispiele Über Feedback würde ich mich freuen!
Sehr gerne :) Wenn du das in einem Projekt nutzt oder Verbesserungsvorschläge oder Fehler gefunden hast würde ich mich über Rückmeldungen freuen...
Hallo Niklas (erlkoenig) Vielen Dank auch von mir für den sehr gelungenen Artikel. Ich habe jedoch noch eine Frage: Als Fazit der 3x Möglichkeiten der Serialisierung schreibst du, dass die memcpy-Variante die "korrekteste" sei. Ich kenne folgende Implementierung der memcpy-Funktion:
1 | void *memcpy(void *dst, const void *src, size_t len) |
2 | {
|
3 | size_t i; |
4 | |
5 | if ((uintptr_t)dst % sizeof(long) == 0 && |
6 | (uintptr_t)src % sizeof(long) == 0 && |
7 | len % sizeof(long) == 0) |
8 | {
|
9 | long *d = dst; |
10 | const long *s = src; |
11 | |
12 | for (i=0; i<len/sizeof(long); i++) |
13 | {
|
14 | d[i] = s[i]; |
15 | }
|
16 | }
|
17 | else
|
18 | {
|
19 | char *d = dst; |
20 | const char *s = src; |
21 | |
22 | for (i=0; i<len; i++) |
23 | {
|
24 | d[i] = s[i]; |
25 | }
|
26 | }
|
27 | return dst; |
28 | }
|
Übersehe ich etwas oder entspricht dies nicht deinem Beispiel Serialisierung durch Pointer-Casts. Beide chasten in einen uint8_t* bzw. char*. Warum sollte dann aber memcpy am "korrektesten sein? (Mal abgesehen, dass diese schön verpackt ist als Funktion und keine lose Schleife darstellt) Ich danke schon einmal im Voraus für eine Antwort. VG Leo
Angenommen man hat eine Pointer-Cast-Variante dieser Art:
1 | typedef struct { |
2 | uint8_t A; |
3 | uint16_t B; |
4 | } DataPacket; |
5 | |
6 | DataPacket* deserialize (const uint8_t* raw) { |
7 | return (DataPacket*) raw; |
8 | }
|
9 | |
10 | int main () { |
11 | const uint8_t raw [100]; |
12 | fread (raw, ...); |
13 | DataPacket* dp = deserialize (raw+1); // 1. Byte überspringen |
14 | printf ("%" PRIu16, dp->B); |
15 | }
|
Dann ist der Lesezugriff auf B ggf. unaligned und führt zu undefined behaviour. Man muss genau darauf achten dass der umgecastete Pointer korrektes Alignment hat; memcpy() vermeidet das komplett. Würde man z.B. uint16_t statt uint8_t nutzen weil man ein Interface hat das immer 16bits auf einmal überträgt, verletzt das außerdem die strict aliasing regel, ist dann also undefined behaviour egal ob korrektes alignment oder nicht. Eine 100% konforme memcpy()-Implementation castet auf einen char-Typ und kopiert char-weise, so wie der else-Zweig in deinem Code. Konkrete Implementationen der C Standard Library können das optimieren wie im if-Zweig, aber das ist dann eben nicht portabel und eben für die eine Plattform der C-Library spezifisch. Tatsächlich ist dieser Code dem Standard nach sogar falsch, denn er verletzt die strict aliasing Regel - mit etwas Pech könnte der Compiler ihn kaputt optimieren. Daher sollte man so etwas im user code lieber nicht machen.
Hallo Niklas, vielen Dank für die schnelle Antwort. Einen schönen Abend noch. VG Leo
Super Artikel, vielen Dank. Denke, das klärt für viele Leute einiges auf. Planst du, die Lib auch für Floating Point zu implementieren?
Danke :) Floating-Point-Unterstützung plane ich derzeit nicht, weil das ziemlich kompliziert wäre. Wenn du eine portable Routine hast, die float <-> uint8_t[4] o.ä. konvertiert, kann ich die natürlich sehr gerne einbauen. Da ja der C++-Standard kein bestimmtes Float-Format vorschreibt, man in den serialisierten Daten aber vermutlich IEEE 754 haben möchte, müsste man mithilfe der Standard Floating-Point-Funktionen (frexp & Co) die floats auseinandernehmen und daraus IEE754-Floats konstruieren. Die Frage ist dann auch, ob der Compiler das wegoptimiert falls die Plattform doch IEE754 nutzt...
Wie sieht es mit überlappenden Kopieren aus? Wäre da nicht die Kopierrichtung zu beachten?
Du meinst, das "rohe" Byte-Array und das Objekt per "union" auf den selben Speicherbereich legen? Das ist i.A. kaum machbar, da beide Repräsentationen unterschiedlich groß sein können, sich dies auch bei den einzelnen Elementen unterscheiden kann und man daher später benötigte Eingabedaten überschreiben würde.
Beispiel: Ich möchte in einem Textpuffer ganz am Beginn ein Zeile mit 80 Zeichen einfügen. Der Puffer ist groß genug den neuen und den ursprünglichen Text aufzunehmen. Also beginne ich den ursprünglichen Text 80 Bytes nach unten zu schieben, bevor ich den neuen Text ganz oben einfüge.
1 | char puffer [1000]; |
2 | |
3 | memcpy (puffer + 80, puffer, 1000-80); |
Wird nicht funktionieren vom Pufferanfang bis zum Pufferende kopiert wird, oder? In der anderen Richtung sollte es funktionieren.
GEKU schrieb: > In der anderen Richtung sollte es funktionieren. Ja, mit memmove()... Aber Quelle und Ziel bestehen aus "char", welche immer gleich groß sind. Beim (De)serialisieren einer beliebigen Datenstruktur können die Größen der Datenelemente auf beiden Seiten prinzipiell beliebig unterschiedlich sein, es wird Padding eingefügt und Integer-Größen aufgerundet. Es kann immer irgendeine komplizierte Überlappung geben, sodass einfach nur rückwärts übertragen nicht reicht. Zudem war eigentlich das Ziel, zumindest auf die "raw" Daten immer vorwärts ("streng monoton") zuzugreifen, damit die einzelnen Elemente immer direkt ein/aus-gegeben werden können.
GEKU schrieb: > Danke, es gibt für alles ein Lösung! Hm, wo ist jetzt der Zusammenhang zu Thread/Artikel/Library?
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.