Artikel und Bibliothek zu Serialisierung

OP Persönliche Seite #5335199
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!
Gast #5341552
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
OP Persönliche Seite #5341568
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.
OP Persönliche Seite #5361390
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...
Gast #5956499
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.
OP Persönliche Seite #5956556
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren