Anregung gesucht



  • Guten Tag,

    ich bin auf der Suche nach einer eleganten Lösung, grüble mir aber einen ab ohne auf eine akzeptable Lösung zu kommen. Folgendes:

    Ich empfange Telegramme die gleich lang sind und z.B. (fiktiv hier mal 18 Zeichen) so aussehen:

    osIks7sAjdifMNvcuh
    83KFifjncvuaKSif2n
    ...
    

    Hinter jedem Zeichen steckt ein eigener 6-Bit-ASCII-Code. D.h. das A ist keine 0x41 sondern z.B. eine b110101 / 0x35. Das ist soweit kein Problem, die Nachricht wird von mir entsprechend umgewandelt.
    Mein Problem besteht darin, das verschieden Nachrichtentypen existieren, die anders aufgebaut sind. Allen gemein ist ein 6-Bit Header an dem man den Typ erkennt. Beispiel:

    Aufbau Nachricht 1:

    6 Bit Header | 2 Bit Feld reserviert | 25 Bit Beschreibung | 9 Bit Richtung | ... (Insgesamt 18 Zeichen * 6 Bit = 108 Bit
    

    Ich muss also ausgehend vom Typ der Nachricht irgendwie an die einzelnen Bits rankommen, da hier z.B. bei "Beschreibung" 4 Bit vom zweiten Zeichen (jedes hat ja 6 Bit) und 21 weitere Bit verwendet werden, die wiederum weitere Zeichen umfassen. Dass das mit entsprechenden Operatoren geht ist mir bewusst, nur wie würdete ihr daran gehen? Wie würdet ihr die Nachricht speichern und zerlegen?

    Ich dachte z.B. an ein char array mit 18 Zeichen:

    char arr[18];
    

    Nur wie dann am besten an die bits kommen?


  • Mod

    Du benutzt Bitmasken:

    // 252 = b11111100
    char wert_der_ersten_sechs_bit = (erstes_byte & 252) >> 2;  // stillschweigende Voraussetzung: char hat 8 Bit.
    


  • Wie wär's mit std::uint8_t, dann brauchste nichts mehr stillzuschweigen.. abgesehen davon: gibt's für sowas nicht bitfields?



  • cooky451 schrieb:

    abgesehen davon: gibt's für sowas nicht bitfields?

    Nein, dafür gibt es vector<bool> *rofl*



  • cooky451 schrieb:

    Wie wär's mit std::uint8_t, dann brauchste nichts mehr stillzuschweigen.. abgesehen davon: gibt's für sowas nicht bitfields?

    Du meinst bitsets? Daran hatte ich auch schon gedacht.
    Aber ich muss erwähnen: Es muss immer noch performant sein und so wie ich das sehe müsste ich das bitset erst entsprechend initialisieren.

    Und ich sehe auch keine Methoden wie "getValue(30,70)" (liefert die bits 30-70 als int)


  • Mod

    cooky451 schrieb:

    gibt's für sowas nicht bitfields?

    Wie würdest du sie hier benutzen? Ich würde mich jedenfalls sehr unwohl fühlen, wenn du irgendwelche Casts oder Union-Hacks vorschlägst, denn an Bitfields ist praktisch das gesamte Layout implementation defined. Mathematik funktioniert hingegen immer richtig und sollte effektiv zum gleichen oder ähnlichen Maschinencode führen.

    huchWieAnregend schrieb:

    cooky451 schrieb:

    Wie wär's mit std::uint8_t, dann brauchste nichts mehr stillzuschweigen.. abgesehen davon: gibt's für sowas nicht bitfields?

    Du meinst bitsets?

    Bit-fields sind noch einmal etwas anderes. Mal googeln. Ein wenig bekanntes und auch wenig genutztes Feature der Sprache.



  • Ja, hab gerade mal nachgeguckt, bitfields war ne doofe Idee. Bleibt wohl nur uint8_t[18] mit vielleicht einer Klasse drum rum.



  • Aber gerade für so etwas wurden doch bitfields (Bitfelder) erfunden, d.h. aus

    6 Bit Header | 2 Bit Feld reserviert | 25 Bit Beschreibung | 9 Bit Richtung | ... (Insgesamt 18 Zeichen * 6 Bit = 108 Bit

    wird

    struct Message
    {
      unsigned Header      : 6;
      unsigned Reserved    : 2;
      unsigned Description : 25;
      unsigned Direction   : 9;
    };
    

    Klar müßte man evtl. die einzelnen Daten noch weiter aufsplitten, z.B. die Beschreibung (Description), aber die Grundstruktur wäre schon mal vom Compiler behandelt.
    Die Einschränkung die noch besteht ist, daß ein Bitfeld nur aus einer bestimmten Anzahl Bits bestehen kann (ich meine max. 32). Dann muß man nur hingehen und mehrere Sub-Strukturen definieren:

    struct Message
    {
       struct Message1
       {
          // ...
       } Message1;
    
       struct Message2
       {
          // ...
       } Message2;
    };
    


  • @Th69 Ja, dafür wurden sie erfunden, aber leider hat man sie so "portabel" gemacht, dass sie völlig nutzlos geworden sind. Der Compiler darf da alles padden und sogar umordnen wie er will, ergo kann man nicht einfach die bytes rein kopieren, ergo völlig nutzlos.


  • Mod

    cooky451 schrieb:

    @Th69 Ja, dafür wurden sie erfunden, aber leider hat man sie so "portabel" gemacht, dass sie völlig nutzlos geworden sind. Der Compiler darf da alles padden und sogar umordnen wie er will, ergo kann man nicht einfach die bytes rein kopieren, ergo völlig nutzlos.

    Zum Verwalten der Daten an sich wären sie gut geeignet (sofern die Größe der Gesamtstruktur eine Rolle spielt). Das Problem ist hier, dass die Daten in einem bestimmten Format serialisiert wurden, welches nicht kompatibel mit dem internen Aufbau sein muss, so dass ein Rechenschritt wie oben gezeigt nötig wird.



  • Das Problem ist halt, dass die Daten wohl eher in "nativen" Typen verwaltet werden. Der Speicher auf dem System wird ja wohl nicht so gering sein, dass man die paar Byte sparen muss, und es ist wesentlich effizienter. Somit sehe ich nicht wirklich einen nutzen von Bitfields hier. (Und leider auch kaum im allgemeinen Fall.)



  • .. ich könnte mir einen selbst gebauten Bitstream vorstellen, der bei der konkreten Nachricht etwa so benutzt wird:

    ibitstream in(/*..*/); // Input Bit Stream
        int x, y, z;
        in >> 6-bits >> x >> 2-bits >> reserved >> 25-bits >> y >> 8-bits >> z /* >> usw. */;
    

    Die Minuszeichen sind 'echte' Minuszeichen und der Ausdruck ' 6-bits ' generiert einen Ausdruck der in dem Stream die aktuell zu lesende Bitsize auf 6 setzt.

    Zum Thema unterschiedliche Nachrichten und die ersten 6Bit entscheiden den Typ - fällt mir ein: Factory-Funktion mit zur Laufzeit polymorphen Nachricht-Objekten. Und für die Aktion, die diese Nachrichten dann auslösen evt. das Visitor-Pattern.

    @huchWieAnregend: wenn Du mehr wissen willst, so musst Du Dich noch mal melden.

    Gruß
    Werner



  • Dann doch lieber ein Manipulator ala:

    stream >> bits(5) >> x;
    

    Man muss nicht alles mit DSELs erschlagen.



  • Werner Salomon schrieb:

    .. ich könnte mir einen selbst gebauten Bitstream vorstellen, der bei der konkreten Nachricht etwa so benutzt wird:

    ibitstream in(/*..*/); // Input Bit Stream
        int x, y, z;
        in >> 6-bits >> x >> 2-bits >> reserved >> 25-bits >> y >> 8-bits >> z /* >> usw. */;
    

    Die Minuszeichen sind 'echte' Minuszeichen und der Ausdruck ' 6-bits ' generiert einen Ausdruck der in dem Stream die aktuell zu lesende Bitsize auf 6 setzt.

    Zum Thema unterschiedliche Nachrichten und die ersten 6Bit entscheiden den Typ - fällt mir ein: Factory-Funktion mit zur Laufzeit polymorphen Nachricht-Objekten. Und für die Aktion, die diese Nachrichten dann auslösen evt. das Visitor-Pattern.

    @huchWieAnregend: wenn Du mehr wissen willst, so musst Du Dich noch mal melden.

    Gruß
    Werner

    Also ich hab das jetzt so gemacht dass ein std::vector eingesetzt wird, wo dann std::uint8_t enthalten sind. Da der Bit-Strom eh nur 1x sequentiell von vorne nach hinten gelesen wird, arbeite ich jetzt mit einem Offset, Shiften und Bitmasken und kann jetzt sagen

    "gib mir 6 bit"
    "gib mir 2 bit" (also das 7. und 8. Bit)
    "gib mir 30 bit"

    Das geschieht bei mir in Abhängigkeit vom Nachrichtentyp, d.h. aus einer Zeichenkette wird eine entsprechende Nachricht als Objekt einer Klasse erstellt.

    Was mich jetzt interessiert: Wie meinst du das mit dem Visitor-Pattern 😕



  • huchWieAnregend schrieb:

    Also ich hab das jetzt so gemacht dass ein std::vector eingesetzt wird, wo dann std::uint8_t enthalten sind. Da der Bit-Strom eh nur 1x sequentiell von vorne nach hinten gelesen wird, arbeite ich jetzt mit einem Offset, Shiften und Bitmasken

    Was spricht gegen einen vector<bool>? Das ist einfacher und genauso lahm (und benötigt genauso wenig Speicher). Dann sparst du dir viel Aufwand.



  • vector<doom> schrieb:

    huchWieAnregend schrieb:

    Also ich hab das jetzt so gemacht dass ein std::vector eingesetzt wird, wo dann std::uint8_t enthalten sind. Da der Bit-Strom eh nur 1x sequentiell von vorne nach hinten gelesen wird, arbeite ich jetzt mit einem Offset, Shiften und Bitmasken

    Was spricht gegen einen vector<bool>? Das ist einfacher und genauso lahm (und benötigt genauso wenig Speicher). Dann sparst du dir viel Aufwand.

    Ich wüsste jetzt nicht, wieso meine Version langsam sein sollte? Ich muss eh jedes Zeichen von ASCII nach 6BitASCCi konvertieren, da speichere ich das Ergebnis gleich im Vector mit fester Größe und anschließend zieh ich mir die Bits da raus.

    Einen vector<bool> zu machen ist doch umständlich, dann müsstest du von ASCII nach 6Bit ASCCI nach vector<bool> und später dann (du willst ja Zahlenwerte haben) müsste man die binäre Kette die man sich aus den Vector zieht wieder in einen int umwandeln.


Anmelden zum Antworten