Forum: Compiler & IDEs Union zum casten nutzen?


von p41145 (Gast)


Lesenswert?

Hallo,

über der seriellen Schnittstelle wird 8bit Datenkette versendet,
die als 10bit Zeichenkette interpretiert werden soll.

aus : abcdefgh ijklmnop qrstuvwx ....
wird: abcdefghij klmnopqrst uvwx....

meine Lösung um ohne bitshifting die Variablen umzuwandeln:
1
typedef struct {
2
  uint16_t b0 : 10;
3
  uint16_t b1 : 10;
4
  uint16_t b2 : 10;
5
  uint16_t b3 : 10;
6
} uint10b_t;
7
8
typedef union {
9
  uint10b_t data10;
10
  uint8_t data8[5];
11
} data_u;
12
13
int main(void)
14
{
15
  data_u data;
16
  data.data8[0] = 255;
17
  data.data8[1] = 255;
18
  data.data10.b0; // enthält 1023
19
  data.data10.b1; // enthält 63
20
}

Soweit läuft es auch wie gewollt.
Nachteil ist jedoch dass man die 10bit Variablen nicht in einer
Schleife sondern einzeln bearbeiten muss, da man kein Bit-Array
erstellen kann, und ich deshalb den Umweg mittels struct gehe.

Als ich mich genauer damit befasst habe, habe ich festgestellt,
dass dieser Weg nicht elegant ist, da das alignment vom Compiler
nicht garantiert wird.

Ist das "unleserliche" bit-shifting der einzige Weg, der wirklich
zulässig ist, oder gibt es noch andere Lösungen um sowas zu casten?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

p41145 schrieb:
> meine Lösung um ohne bitshifting die Variablen umzuwandeln:

Was meinst Du, was der Compiler anstellt, wenn er auf Deine Bitfelder 
zugreift?

von p41145 (Gast)


Lesenswert?

Ja klar mach der Compiler das im Endeffekt per bitshifting,
nur mit dem Unterschied, dass man den Code besser lesen kann.

Es kann jedoch passieren, dass ein anderer Compiler die 10bits
in einem uint16_t packt und nicht mehr shiftet.

Deshalb mein Posting, vielleicht hat jemand einen anderen Ansatz.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

p41145 schrieb:
> Es kann jedoch passieren, dass ein anderer Compiler die 10bits
> in einem uint16_t packt und nicht mehr shiftet.

Das kann nur passieren, wenn Du Dein Bitfeld nicht als "gepacktes" 
Bitfeld deklarierst.

Siehe
1
__attribute__((packed))

von A. S. (Gast)


Lesenswert?

Normalerweise gibt es bitfelder nur gepackt in Einheiten des verwendeten 
Typs.

Du hast einen 16 bit-typ, dann sollten darin 3 5-bit-felder möglich 
sein, aber nur ein 10bit Feld. Zum nächsten sollt 6bit frei bleiben

von MaWin O. (Gast)


Lesenswert?

Typumwandlung mit union ist Undefined Behavior.
Man darf nicht in ein Union-Feld etwas schreiben und es aus einem 
anderen Union-Feld rücklesen.

von Nop (Gast)


Lesenswert?

Ma W. schrieb:
> Typumwandlung mit union ist Undefined Behavior.

Nicht seit C99 in C.

> Man darf nicht in ein Union-Feld etwas schreiben und es aus einem
> anderen Union-Feld rücklesen.

Doch, darf man. Type-punning per Union ist erlaubt. Im Zusammenhang mit 
Bitfeldern ist das Ergebnis dann allerdings implementationsabhängig, 
weil nicht spezifiziert ist, wie herum die Bits eingepackt werden.

Wenn man portablen Code haben will, sollte man Bitfelder ausschließlich 
zum Laden und Speichern nutzen.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Nop schrieb:
> Ma W. schrieb:
>> Typumwandlung mit union ist Undefined Behavior.
>
> Nicht seit C99 in C.
>
>> Man darf nicht in ein Union-Feld etwas schreiben und es aus einem
>> anderen Union-Feld rücklesen.
>
> Doch, darf man. Type-punning per Union ist erlaubt.
Da wir nicht wissen, ob der TO C oder C++ verwendet, ist die Frage nach 
dem "UB or not UB" etwas muessig.

https://stackoverflow.com/questions/11373203/accessing-inactive-union-member-and-undefined-behavior/11996970#11996970
1
The confusion is that C explicitly permits type-punning through a union,
2
whereas C++ (C++11) has no such permission.

von p41145 (Gast)


Lesenswert?

Kaj G. schrieb:
> Da wir nicht wissen, ob der TO C oder C++ verwendet, ist die Frage nach
> dem "UB or not UB" etwas muessig.

Kurze Antwort: Ich benutze C (gnu99)

Also sehe ich es richtig, es ist nicht zulässig,
funktioniert aber trotzdem Compilerabhängig?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Nop schrieb:
> Ma W. schrieb:
>> Typumwandlung mit union ist Undefined Behavior.
>
> Nicht seit C99 in C.

In auch nicht mit gcc.  Da steht im Kleingedruckten, dass Type-Punning 
per Union explizit unterstützt wird; auch vor C99.  gcc verwendet dieses 
Feature bei der Implementation der libgcc z.B. für Fixed-Point und 
Floating-Point Support.

von Nop (Gast)


Lesenswert?

p41145 schrieb:

> Also sehe ich es richtig, es ist nicht zulässig,

Doch, ist es in C99 und später. gnu99 setzt auf C99 nur noch ein paar 
Erweiterungen drauf.

Aber es ist nicht portabel, weil nicht festgelegt ist, wie herum der 
Compiler die Bitfields implementiert - MSB oben oder unten und Endianess 
seien genannt. Deswegen sollte man Bitfields für sowas nicht verwenden, 
sondern das mit richtigen Umwandlungen machen.

von Peter D. (peda)


Lesenswert?

p41145 schrieb:
> Nachteil ist jedoch dass man die 10bit Variablen nicht in einer
> Schleife sondern einzeln bearbeiten muss

Und man darf sich auch nicht im Listing anschauen, welch riesen Bohei 
der Compiler für Bitfelder veranstalten muß.

Ich würds einfach mit ner Funktion machen, dann geht auch ne Schleife.
1
uint16_t get10bit( uint8_t *p, uint8_t i )
2
{
3
  return *(uint16_t *)(p+i) >> i*2 & 0x03FF;
4
}

P.S.:
Der Code ist für Little-Endian.

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

Peter D. schrieb:
> Ich würds einfach mit ner Funktion machen, dann geht auch ne
> Schleife.uint16_t get10bit( uint8_t *p, uint8_t i )
> {
>   return *(uint16_t *)(p+i) >> i*2 & 0x03FF;
> }

Bist Du sicher daß der Code das macht was gefordert ist?

von p41145 (Gast)


Lesenswert?

Bernd K. schrieb:
> Bist Du sicher daß der Code das macht was gefordert ist?

Ich kann zumindest bestätigen, dass es bis zu 5x8bit zu
4x10bit gut funktioniert. Man muss den Pointer alle vier
10bits nochmal um ein byte versetzen dann klappt es auch
für größere Arrays.

@Peter Dannegger:
Die Priorität der Operatoren finde ich nicht trivial,
schwer so zu lesen, aber ein guter Ansatz.
1
uint16_t get10bit( uint8_t *p, uint8_t i ){
2
  uint16_t * ptr;
3
  ptr = (uint16_t *) (p + i + (i / 4));
4
  return  (*ptr >> ((i&3)*2)) & 0x03FF;
5
}
6
7
void put10bit( uint8_t *p, uint8_t i, uint16_t data){
8
  uint16_t * ptr;
9
  uint16_t mask = 0x3FF;
10
  uint8_t shift = ((i & 3) * 2);
11
  ptr = (uint16_t *) (p + i + (i / 4));
12
  mask <<= shift;
13
  data <<= shift;
14
  *ptr &= ~mask;
15
  *ptr |= data;
16
}

von Peter D. (peda)


Lesenswert?

p41145 schrieb:
> Die Priorität der Operatoren finde ich nicht trivial,
> schwer so zu lesen, aber ein guter Ansatz.

Ich muß zugeben, ich hatte erst ein switch/case erstellt und dann 
geschaut, wie sich die 4 Case unterscheiden.

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.