Bitgefummel
-
Tja, schei...e

Das oben war natürlich nur ein kurzes Beispiel. Mit der echten Nachricht (message_2_t) klappt es wieder nicht (auch nicht mit dem c-Compiler). Die union ist 12 Byte anstatt der erwarteten 9 Byte. Hier ist wohl das Problem, dass ich zwischendurch größere Typen benötige (uint16_t).
#include <stdio.h> #include <stdint.h> typedef union { struct { uint8_t field1 : 1; uint8_t field2 : 4; uint8_t field3 : 3; }; struct { uint8_t field1_padding : 1; uint8_t field4 : 7; }; } __attribute__((packed)) message_t; union { struct { uint8_t field1 : 2; uint8_t field2 : 5; uint8_t field3 : 1; uint8_t field4 : 5; uint16_t field5 : 12; uint16_t field6 : 12; uint16_t field7_1 : 11; uint8_t field8 : 6; uint8_t field9 : 7; uint8_t field10 : 8; uint8_t field11 : 1; uint8_t field12 : 2; }; struct { uint8_t __field1 : 2; uint8_t __field2 : 5; uint8_t __field3 : 1; uint8_t __field4 : 5; uint16_t __field5 : 12; uint16_t __field6 : 12; uint8_t field7_2 : 7; uint8_t field7_3 : 4; uint8_t __field8 : 6; uint8_t __field9 : 7; uint8_t __field10 : 8; uint8_t __field11 : 1; uint8_t __field12 : 2; }; } __attribute__((packed)) message_2_t; int main(void) { printf("%zu\n", sizeof (message_t)); printf("%zu\n", sizeof (message_2_t)); return 0; }Hat noch wer eine Idee?
Grüße, Vinzent
-
Vinzent72 schrieb:
Hat noch wer eine Idee?
Grüße, Vinzent
KJa. Erzähnel, WAS DU machen willst, nicht WIE.
-
Vinzent72 schrieb:
Das oben war natürlich nur ein kurzes Beispiel. Mit der echten Nachricht (message_2_t) klappt es wieder nicht (auch nicht mit dem c-Compiler). Die union ist 12 Byte anstatt der erwarteten 9 Byte. Hier ist wohl das Problem, dass ich zwischendurch größere Typen benötige (uint16_t).
Oder daß die structs nicht packed sind, sondern nur die union.
-
volkard schrieb:
Vinzent72 schrieb:
Hat noch wer eine Idee?
Grüße, Vinzent
KJa. Erzähnel, WAS DU machen willst, nicht WIE.
WAS ich machen möchte ist, Nachrichten (wie oben beschrieben) aus einem Socket lesen und dann verarbeiten (dazu muss ich die Inhalte kennen). Das ganze auch vice versa, d.h. Nachrichten erstellen und über den Socket versenden.
Ich wollte mir das manuelle Bitgefummel (&, |, <<) halt sparen durch entsprechen ausgerichtete ('aligndte' wollt ich jetzt nicht schreiben ;)) structs.
Grüße, Vinzent
-
Eine Mischung aus beidem. Wenn du schreibst
uint8_t field1 : 2; uint8_t field2 : 5; uint8_t field3 : 1; // zusammen 8 uint8_t field4 : 5; // hier 13 -- keine Bytegrenze. uint16_t field5 : 12; // Aber hier neuer Typ uint16_t field6 : 12;hast du die Bruchstelle falsch. Vermieden kriegst du das in diesem Fall nur, indem du alles nach field3 in einen großen uint64_t packst -- sonst liegt immer etwas auf einer Schnittstelle. Damit aber zwischen den uint8_t am Anfang und den uint64_t am Ende kein Padding vom Compiler eingefügt wird, müssen die Structs dann auch packed sein. Also:
typedef union { struct { uint8_t field1 : 2; uint8_t field2 : 5; uint8_t field3 : 1; uint64_t field4 : 5; uint64_t field5 : 12; uint64_t field6 : 12; uint64_t field7_1 : 11; uint64_t field8 : 6; uint64_t field9 : 7; uint64_t field10 : 8; uint64_t field11 : 1; uint64_t field12 : 2; } __attribute__((packed)); struct { uint8_t __field1 : 2; // __foo sind übrigens für die Implementation reservierte Bezeichner. Du solltest deine unbenutzten Felder anders nennen. uint8_t __field2 : 5; uint8_t __field3 : 1; uint64_t __field4 : 5; uint64_t __field5 : 12; uint64_t __field6 : 12; uint64_t field7_2 : 7; uint64_t field7_3 : 4; uint64_t __field8 : 6; uint64_t __field9 : 7; uint64_t __field10 : 8; uint64_t __field11 : 1; uint64_t __field12 : 2; } __attribute__((packed)); } __attribute__((packed)) message_2_t;...aber das ist schon ein ziemlich kaputter Datentyp. Ich bin mir nicht sicher, ob so was auf ner SPARC läuft.
-
Kürzere Variante:
typedef union { struct { uint8_t field1 : 2; uint8_t field2 : 5; uint8_t field3 : 1; uint64_t field4 : 5; uint64_t field5 : 12; uint64_t field6 : 12; uint64_t field7_1 : 11; uint64_t field8 : 6; uint64_t field9 : 7; uint64_t field10 : 8; uint64_t field11 : 1; uint64_t field12 : 2; } __attribute__((packed)); struct { uint8_t implementation_detail_padding_byte; uint64_t implementation_detail_padding_vorn : 29; uint64_t field7_2 : 7; uint64_t field7_3 : 4; } __attribute__((packed)); } __attribute__((packed)) message_2_t;Auch möglich: das zweite Struct als
struct { uint64_t implementation_detail_padding_vorn : 37; uint64_t field7_2 : 7; uint64_t field7_3 : 4; } __attribute__((packed));schreiben. Ersteres ist etwas sauberer, wegen Layout-Kompatibilität und so, aber bei so einer Sache ist es etwas müßig, sich um Sauberkeit zu zanken.
-
seldon schrieb:
// __foo sind übrigens für die Implementation reservierte Bezeichner. Du solltest deine unbenutzten Felder anders nennenoder gleich unbenannt lassen.
-
Structs und Bitfelder auf irgendwelche Daten zu mappen würde ich lassen, weil maschinen und compilerabhängig. Die sicherste Methode ist immer noch, mit Ausmaskieren und Shiften an die Bits zu kommen. Das klappt überall.
-
Z schrieb:
Structs und Bitfelder auf irgendwelche Daten zu mappen würde ich lassen, weil maschinen und compilerabhängig. Die sicherste Methode ist immer noch, mit Ausmaskieren und Shiften an die Bits zu kommen. Das klappt überall.
Volle Zustimmung!
So schwer muss man sich das auch nicht machen. Man könnte sich z.B. eine "bit_reader"-Klasse bauen, die einem die Dinger aus einer unsigned-char-Sequenz holen. Allerdings müsste man dazu noch wissen, in welcher Reihenfolge die Bits betrachtet werden. Da gibt es im Wesentlichen zwei Varianten, wovon die populärere die höherwertigen Bits zuerst liest.
Weil ich gerade Bock drauf hatte:
#include <iostream> #include <iomanip> #include <cstdint> #include <stdexcept> #include <algorithm> template<class OctetInputIter> class bit_reader { OctetInputIter next; OctetInputIter end; std::uint32_t data; unsigned dlen; void read_octet() { if (next==end) throw std::runtime_error("end of bitstream"); data = (data << 8) | (*next & 0xFFu); dlen += 8; ++next; } public: bit_reader(OctetInputIter n, OctetInputIter e) : next(n), end(e) , data(0), dlen(0) {} std::uint32_t read(unsigned nbits) { std::uint32_t result = 0; while (nbits>0) { while (dlen<nbits && dlen<=24) read_octet(); // how many bits to move from data to result? int chunk = std::min(dlen,nbits); dlen -= chunk; nbits -= chunk; result = (result << chunk) | (data >> dlen); data &= (1u << dlen) - 1; } return result; } }; template<class OctetInputIter> bit_reader<OctetInputIter> make_bit_reader(OctetInputIter begin, OctetInputIter end) { return bit_reader<OctetInputIter>(begin,end); } int main() { using namespace std; unsigned char dat[] = {0x12,0x34,0x56}; auto br = make_bit_reader(dat+0,dat+sizeof(dat)); cout << hex << br.read(4) << endl; cout << hex << br.read(8) << endl; cout << hex << br.read(4) << endl; cout << hex << br.read(8) << endl; }Dieses Beispiel scheint jedenfalls zu funktionieren:
me@compi ~/Documents/ccpp $ g++ -std=c++0x bit_reader.cpp me@compi ~/Documents/ccpp $ a 1 23 4 56Ich garantiere nicht dafür, dass da nicht vielleicht noch andere Bugs drin sind.

-
Naja, das hängt jetzt ein bisschen davon ab, was da noch kommt - TE ist bislang mit Details ziemlich zurückhaltend. Es scheint sich um etwas zu handeln, bei dem Platz ziemlich teuer ist -- wenn das Netzwerkverkehr ist, ist es wohl unproblematisch, das beim Lesen einmal auszuklamüsern und in eine vernünftige Form zu bringen, aber wenn das Zeug beispielsweise einem kleinen ARM per UART-DMA oder so übergeben wird, können Speicher und CPU-Zeit ebenfalls knapp sein, und dann könnte es wenig sinnvoll sein, da noch weitere Objekte drumzubasteln.
Aber: Wenn Portabilität gewollt ist, läuft man mit diesem Zeug noch in ganz andere Probleme. Wie sieht das mit 11 Bit breiten Bitfeldern innerhalb eines uint64_t in Bezug auf Bytesex aus? Das ist nicht ganz trivial zu beantworten. Was auf der einen Maschine in Bits 7-18 lag, liegt auf der anderen in Bits...äh...63, 48-56 und 40-42, glaube ich. Das unabhängig davon, ob man mit Bitfeldern arbeitet oder das von Hand herumshiftet (sofern man die Bytes nicht einzeln shiftet und zusammensetzt).
Ein ganz anderes Problem ist noch, dass die Allokation der Bitfelder innerhalb eines Datentyps implementation-defined ist. Ob also die Daten nach dem nächsten Compilerupdate noch an der selben Stelle landen, ist nicht sichergestellt. Wenn da unterschiedliche Versionen miteinander reden sollen oder die Nachrichten irgendwo gespeichert werden, hast du ein echtes Problem.
Ich würde das ganze vermutlich in ein POD-Struct verpacken, das nur die Bytes hält und Methoden zum Zugriff zur Verfügung stellt (ja, PODs dürfen das). Etwa so:
struct message_t { // Alles public, um POD zu bleiben unsigned char data; uint8_t field1() const { return data & 0x01; } uint8_t field2() const { return data >> 1 & 0x0f; } uint8_t field3() const { return data >> 5 ; } uint8_t field4() const { return data >> 1 ; } void field1(uint8_t x) { data |= x & 0x01 ; } void field2(uint8_t x) { data |= (x & 0x0f) << 1; } void field3(uint8_t x) { data |= x << 5; } void field4(uint8_t x) { data |= x << 1; } } __attribute__((packed)); struct message_2_t { unsigned char bytes[9]; uint8_t field1() const { return bytes[0] & 0x03; } uint8_t field2() const { return bytes[0] >> 2 & 0x1f; } uint8_t field3() const { return bytes[0] >> 7 ; } uint8_t field4() const { return bytes[1] & 0x1f; } uint16_t field5() const { return (bytes[1] >> 5) | (bytes[2] << 3) | ((bytes[3] & 0x01) << 11); } ... } __attribute__((packed));...das ist zwar ein Haufen Arbeit, dafür funktioniert es am Ende aber überall gleich (wo __attribute__((packed)) existiert). Ob man sich etwas TMP-Kram baut, um die Bitmasken und Shiftlängen vom Compiler ausrechnen zu lassen, ist eine Überlegung wert, und mindestens die Breite mit den Masken zu verknüpfen ist sinnvoll, wenn man zumindest Zweierkomplementdarstellung voraussetzen kann (merke: 0x1f == (1 << 5) - 1).
Die Arbeit darfste aber selbst machen.
-
seldon schrieb:
Ich würde das ganze vermutlich in ein POD-Struct verpacken, das nur die Bytes hält und Methoden zum Zugriff zur Verfügung stellt (ja, PODs dürfen das).
Erst mal danke für eure Antworten.
Mit ist schon klar, dass das Bit packing unportabel (gefährlich) ist, aber da ich ca. 250 unterschiedliche solcher Nachrichten zu dekodieren habe, habe ich erst mal nach der am schnellsten zu kodierenden Methode gesucht.
Werde aber wohl nicht um di POD-Struct rumkommen...

Grüße, Vinzent.
-
Vinzen72 schrieb:
Mit ist schon klar, dass das Bit packing unportabel (gefährlich) ist, aber da ich ca. 250 unterschiedliche solcher Nachrichten zu dekodieren habe, habe ich erst mal nach der am schnellsten zu kodierenden Methode gesucht.
Werde aber wohl nicht um di POD-Struct rumkommen...

Ja, 250 verschiedene Nachrichten-Typen sind nervig. Aber ich sehe keine Notwendigkeit für Hacks wie die Bitfeld-Frimelei. Mach das als unsigned char-Array und gut ist. Wenn dir das mit der bit_reader-Klasse nicht gefällt, probier es doch mal damit:
#include <iostream> #include <cstdint> #include <cassert> //----------[ generischer Kram ]---------- // extracts a sequence of bits from an unsigned char array // at arbitrary bit positions (most significant bits first) std::uint32_t get_bits_msbf( const unsigned char data[], unsigned bit_offset, unsigned nbits) { // TODO: Diese Implementierung kann man vielleicht noch vereinfachen. assert(nbits <= 32); if (nbits == 0) return 0u; data += bit_offset / 8; bit_offset &= 7; unsigned tlen = 8 - bit_offset; std::uint32_t temp = *data & (0xFFu >> bit_offset); if (tlen >= nbits) return temp >> (tlen-nbits); while (tlen+8 <= nbits) { ++data; temp = (temp << 8) | (*data & 0xFFu); tlen += 8; } unsigned missing = nbits - tlen; if (missing) { ++data; temp = (temp << missing) | (*data >> (8-missing)); } return temp; } template<unsigned BitOffset, unsigned BitLen, class MessageTag=void> struct field { typedef MessageTag message_tag; static const unsigned bit_offset = BitOffset; static const unsigned bit_len = BitLen; }; template<class MessageTag=void> struct message_ptr { const unsigned char* data; unsigned length; explicit message_ptr(const unsigned char *ptr, unsigned len) : data(ptr), length(len) {} template<unsigned BitOffset, unsigned BitLen> std::uint32_t get(field<BitOffset,BitLen,MessageTag>) const { assert(BitOffset+BitLen <= 8*length); return get_bits_msbf(data,BitOffset,BitLen); } }; //----------[ Anwendungsfall ]---------- namespace mp3_stuff { struct frame_header_tag {}; const field<0,11,frame_header_tag> sync = {}; const field<11,2,frame_header_tag> mpegid = {}; const field<13,2,frame_header_tag> layer = {}; const field<15,1,frame_header_tag> protection_bit = {}; const field<16,4,frame_header_tag> bitrate_index = {}; const field<20,2,frame_header_tag> sampling_rate_index = {}; const field<22,1,frame_header_tag> padding_bit = {}; const field<23,1,frame_header_tag> private_bit = {}; const field<24,2,frame_header_tag> channel_mode = {}; const field<26,2,frame_header_tag> cm_extension = {}; const field<28,1,frame_header_tag> copyrighted_bit = {}; const field<29,1,frame_header_tag> original_bit = {}; const field<30,2,frame_header_tag> emphasis_mode = {}; static const char* const mpegid2str[] = {"MPEG 2.5", "reserved", "MPEG 2", "MPEG 1"}; static const char* const chanmodes[] = {"Stereo", "Joint Stereo", "Dual Channel", "Mono"}; static const long srate[] = {44100,48000,32000}; } // namespace mp3_stuff void test() { using namespace std; using namespace mp3_stuff; unsigned char data[] = {0xFF, 0xFB, 0xA0, 0x64}; message_ptr<frame_header_tag> mptr (data,4); if (mptr.get(sync) == 0x7FF) { cout << "MPEG : " << mpegid2str[mptr.get(mpegid)] << endl; cout << "Layer : " << 4-mptr.get(layer) << endl; cout << "Bitrate index : " << mptr.get(bitrate_index) << endl; const int srdivisor = 4-mptr.get(mpegid); cout << "Sampling rate : " << srate[mptr.get(sampling_rate_index)]/srdivisor << endl; cout << "Channel mode : " << chanmodes[mptr.get(channel_mode)] << endl; } else { cout << "Sync bits invalid\n"; } } int main() { test(); }Ich denke, damit sollte die Fehleranfälligkeit in der Benutzung nicht so groß sein. Man definiert das Format quasi an einer Stelle und überall sonst kann man nicht mehr viel falsch machen, da man per mptr.get(feld) ein Datum ausliest, siehe Zeilen 92-98. Aufgrund des MessageTag-Parameters sollte das auch genügend typsicher sein.
Was mit gerade auffällt: Es macht ja sogar Sinn, statt
getdenoperator->*zu überladen.