Forum: Projekte & Code Artikel und Bibliothek zu Serialisierung


von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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!

von Tobias (Gast)


Lesenswert?

Danke für den Artikel!

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Sehr gerne :) Wenn du das in einem Projekt nutzt oder 
Verbesserungsvorschläge oder Fehler gefunden hast würde ich mich über 
Rückmeldungen freuen...

von Leo (Gast)


Lesenswert?

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

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Leo (Gast)


Lesenswert?

Hallo Niklas,
vielen Dank für die schnelle Antwort.
Einen schönen Abend noch.

VG Leo

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Gerne... ich hoffe es ist hilfreich :)

von Jan K. (jan_k)


Lesenswert?

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?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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...

von GEKU (Gast)


Lesenswert?

Wie sieht es mit überlappenden Kopieren aus? Wäre da nicht die 
Kopierrichtung zu beachten?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von GEKU (Gast)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von GEKU (Gast)


Lesenswert?

Niklas G. schrieb:
> memmove()

Danke, es gibt für alles ein Lösung!

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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
Noch kein Account? Hier anmelden.