Forum: Compiler & IDEs Unbekannte C Syntax in struct und Byte Zugriff


von Josef (Gast)


Lesenswert?

Hallo,

wer kann mir bei einem für mich unbekannten Code helfen.

Was bedeutet die Zahl mit dem Doppelpunkt in der struct?

    struct
    {
        u32 var1 : 8;
        u32 var2 : 8;
        u32 var3 : 1;
        u32 var4 : 1;
    } test;

Und der andere Befehl:
UINT8   variable[2048] ={0};

0[(UINT32*)variable]
1[(UINT32*)variable]

Kann mir jemand sagen was da passiert?

Ich hoffe mir kann jemand helfen :)

LG Josef

von Emv (Gast)


Lesenswert?


von Karl H. (kbuchegg)


Lesenswert?

Josef schrieb:

> Und der andere Befehl:
> UINT8   variable[2048] ={0};
>
> 0[(UINT32*)variable]
> 1[(UINT32*)variable]
>
> Kann mir jemand sagen was da passiert?

Da hat jemand den 'obfuscated Programmer' raushängen lassen um damit 
andere zu verwirren.

Das ist nichts anderes als
1
  ((UINT32*)variable)[0];
2
  ((UINT32*)variable)[1];

wobei das je nachdem in welchem Zusammenhang es vorkommt, auch als
1
  *(UINT32*)variable
2
  *((UINT32*)variable + 1)
besser geschrieben werden könnte. Kommt aber auf den Zusammenhang an, 
was dann klarer ist.

von Josef (Gast)


Lesenswert?

Danke für eure Antworten!

> Das ist nichts anderes als
>   ((UINT32*)variable)[0];
>   ((UINT32*)variable)[1];
>
> wobei das je nachdem in welchem Zusammenhang es vorkommt, auch als
>   *(UINT32*)variable
>   *((UINT32*)variable + 1)
> besser geschrieben werden könnte. Kommt aber auf den Zusammenhang an,
> was dann klarer ist.

Also ruft er aber auch wirklich immer UINT32 also 4 Byteweise auf das 
Array zu und liest somit die ersten 4 Bytes und dann die nächsten 4 
Bytes raus.

Wie kommt man eigentlich auf so ein Konstrukt? In welcher abstrusen 
Literatur findet man so etwas :D

Zum Thema Bitfields ... Wozu macht man sich so etwas? Wenn ich das 
richtig gelesen habe, kann man sich nicht sicher sein wie der compiler 
den Speicher anlegt. Damit weiß man auch nicht ob man Speicherplatz 
spart und von der Geschwindigkeit dürfte es somit auch schlechter sein 
oder genauso als wenn man es nicht macht.
Kann man dann auch mit größeren Werten ein Bitfeld, also eine Variable 
mit der anderen Überschreiben? Wenn Var1 4 Bits hat und ich dort einen 
Wert größer 2^3 reinschreibe, zerschiesst er mir den nächsten? oder 
fängt genau dass, das System ab? Dann wird es ja auch immer langsam wenn 
man drauf zu greift.

von Karl H. (kbuchegg)


Lesenswert?

Josef schrieb:

> Wie kommt man eigentlich auf so ein Konstrukt? In welcher abstrusen
> Literatur findet man so etwas :D

Das ist ein Nebeneffekt, wie Array-Indizierung in C definiert ist
1
    a[i]  <==>   *( a + i )

genau deswegen kannst du ja auch mit Indizierungssyntax auf ein Array 
zugreifen, welches du dir mit malloc allokiert hast, und von dem du nur 
einen Pointer auf das erste Element hast. Aus genau dem gleichen Grund 
funktioniert die 'Übergabe' eines Arrays an eine Funktion, in dem die 
Funktion einen Pointer auf das erste Element eines Arrays hat.

Und es ist auch logisch:
Denn das Element a[i] eines Arrays a ist genau jenes Element, welches 
vom Beginn des Arrays an gerechnet, i Einheiten entfernt ist.

Aber:
Addition ist kommutativ. a+i ist dasselbe wie i+a. Auch bei Pointern und 
Offsets zu Pointern. Daher kann man
1
   a[i]    ===>    *( a + i )
2
3
                       |
4
                       |
5
                       v
6
7
                   *( i + a )
8
9
   i[a]    <======

a[i] ist also dasselbe wie i[a]. Und zwar in genau der gleichen Art und 
Weise, wie es keine Rolle spielt ob du
1
  int* ptr = ....;
2
  int  offset = ....;
3
4
  int value = *( ptr + offset );
schreibst, oder
1
  int value = *( offset + ptr );


Normalerweise benutzt das keiner, weil es unsinnig ist, sich da selbst 
ein Ei zu legen.

von Karl H. (kbuchegg)


Lesenswert?

Josef schrieb:

> Zum Thema Bitfields ... Wozu macht man sich so etwas?

Damit man in einem Byte alle 8 Bit benutzen kann, selbst dann wenn man 8 
Werte hat, die nur 0 oder 1 sein können (also die klassischen Bits).

Schreibst du
1
uint8_t  haveError;
2
uint8_t  haveOverflow;
3
uint8_t  isRed;
4
uint8_t  isGreen;
5
uint8_t  isBlue;
6
uint8_t  frameError;
7
uint8_t  answerReceived;
8
uint8_t  clockwise;
dann verbrauchen all diese Ja/Nein Entscheidungen zusammen 8 Stück 
uint8_t.

Schreibst du das als
1
struct flags_t
2
{
3
  uint8_t  haveError : 1;
4
  uint8_t  haveOverflow : 1;
5
  uint8_t  isRed : 1;
6
  uint8_t  isGreen : 1;
7
  uint8_t  isBlue : 1;
8
  uint8_t  frameError : 1;
9
  uint8_t  answerReceived : 1;
10
  uint8_t  clockwise : 1;
11
} Flags;
dann verbrauchen dieselben Informationen nur einen uint8_t. Und es gibt 
Fälle, da ist dir das wichtiger als der etwas erhöhte Aufwand beim 
zugriff auf die Einzelteile.


(Unter der Annahme, dass ein uint8_t aus 8 Bit besteht, was wohl der 
Regelfall sein wird)

> Wenn ich das richtig gelesen habe, kann man sich nicht sicher
> sein wie der compiler den Speicher anlegt.

Ganz so einfach ist das nicht.
Der Compiler muss das Zeugs schon zusammenpacken. Aber wie er die Bits 
vergibt, das ist seine Sache.

> Damit weiß man auch nicht ob man Speicherplatz spart und von der
> Geschwindigkeit dürfte es somit auch schlechter sein

Wenn der COmpiler überhaupt nicht packen würde, warum soll das dann von 
der Geschwindigkeit her schlechter sein?

> Kann man dann auch mit größeren Werten ein Bitfeld, also eine
> Variable mit der anderen Überschreiben?

Nein.
Definierst du einen Member eines Bitfields mit 3 Bit, dann passen da 
auch nur Werte rein, die sich mit 3 Bit darstellen lassen. Du kannst da 
nichts größeres zuweisen und du kriegst da auch nichts größeres raus.

> Wenn Var1 4 Bits hat und ich dort einen Wert größer 2^3 reinschreibe,
> zerschiesst er mir den nächsten?

Nein

> oder fängt genau dass, das System ab? Dann wird es ja auch immer
> langsam wenn man drauf zu greift.

Natürlich.
Eine alte Regel in der Informatik besagt "Time for Space".
Und das bedeutet, dass man sich oft Speicherplatz mit Laufzeit erkauft 
bzw. umgekehrt Laufzeit holt indem man Speicher opfert. Irgendeinen Tod 
muss man dann eben sterben. Niemand zwingt dich Bitfelder einzusetzen. 
Wenn dir die Laufzeit wichtiger ist als 7 verschwendete Bytes UND 
Zugriffe häufig vorkommen, dann wird man das eben nicht so benutzen.

von Kaj (Gast)


Lesenswert?

Josef schrieb:
> Zum Thema Bitfields ... Wozu macht man sich so etwas?

Mal angenommen du hast ein Programm das schon seit Jahren läuft. Jetzt 
soll das Programm erweitert werden: Das Programm soll jetzt noch einen 
kompletten Zeitstempel (Jahr:Monat:Tag:Stunde:Minute:Sekunde) "nebenbei" 
versenden. Das bestehende kommunikations Protokoll erlaubt dir, eine 
Payload von 4 Byte, nicht mehr!
Dann wäre eine Möglichkeit:
1
struct timestamp_t
2
{
3
  uint8_t Jahr   : 7;
4
  uint8_t Monat  : 4;
5
  uint8_t Tag    : 5;
6
  uint8_t Stunde : 5;
7
  uint8_t Minute : 6;
8
  uint8_t Sekunde: 5;
9
} TimeStamp;
10
// 0000000   0000  00000   00000   000000  00000  <-- 32bit
11
//|  Jahr | Monat | Tag | Stunde | Minute |Sekunde
Die Sekunden werden durch 2 geteilt. Damit wird 1 Bit gespart und der 
Fehler im Timestamp ist akzeptabel gering (je nach Anforderung)(Muss 
beim Empfänger natürlich wieder mit 2 Multipliziert werden). Jahr begint 
beim Jahr 2000. 7bit reichen von 0 bis 127, also bis zum Jahre 2127, 
wobei bei der Übertragung die erste Ziffer, also die "2" weggelassen 
wird, da sich diese Ziffer in absehbarer Zeit nicht ändert. (Ggf. könnte 
man auch dem Jahr nur 6bit statt 7bit(geht dafür dann halt nur bis 
2063), und den Sekunden dafür 6bit statt 5bit spendieren)
Das mit den Sekunden und dem Weglassen der ersten Ziffer bei dem Jahr 
ist in diesem Falle eine Protokoll vereinbarung.

Um also deine Frage zu beantworten:
Der Zweck heiligt die Mittel!

Statt einem Bitfeld kannst du natürlich auch einfach einen uint32_t 
nehmen und wie ein Weltmeister shiften. Wenn ich mich aber so an den 
einen oder anderen Forenbeitrag erinnere (wo versucht wurde eine 0 zu 
shiften
(z.b. 0 << 5) ) bevorzuge ich dann doch lieber das Bitfeld (lesbarer und 
weniger fehleranfällig).

Grüße

von Da D. (dieter)


Lesenswert?

Kaj schrieb:
> Mal angenommen du hast ein Programm das...

Was soll uns dieses längliche Märchen von maximal miesem Code sagen? Was 
ein Bitfeld ist, lässt sich auch in einem Satz erklären.

von Kaj (Gast)


Lesenswert?

Da Dieter schrieb:
> Was
> ein Bitfeld ist, lässt sich auch in einem Satz erklären.
Es ging nicht darum was ein Bitfeld ist, sondern wozu man es benutzen 
kann!

Da Dieter schrieb:
> maximal miesem Code
Noch nie mit Kunden und Code/Hardware gearbeitet die nicht deiner Feder 
entsprungen ist, was?

von tictactoe (Gast)


Lesenswert?

Karl Heinz schrieb:
> a[i] ist also dasselbe wie i[a].

Insbesonders kann man sich mit i["foobar"] die Buchstaben einzeln aus 
einem String greifen :-)

von Mark B. (markbrandis)


Lesenswert?

Karl Heinz schrieb:
> Das ist nichts anderes als
>
>   ((UINT32*)variable)[0];
>   ((UINT32*)variable)[1];

Und wozu genau soll das gut sein, wenn da keinerlei Zuweisung erfolgt?

von Yalu X. (yalu) (Moderator)


Lesenswert?

Mark Brandis schrieb:
> Karl Heinz schrieb:
>> Das ist nichts anderes als
>>
>>   ((UINT32*)variable)[0];
>>   ((UINT32*)variable)[1];
>
> Und wozu genau soll das gut sein, wenn da keinerlei Zuweisung erfolgt?

Die Semikolons sind ihm unbeabsichtigterweise reingerutscht. Aus dem
Kontext geht ja klar hervor, dass es hier um Ausdrücke und nicht um
Anweisungen geht.

von Michi (Gast)


Lesenswert?

Hallo,

Karl Heinz schrieb:

> Addition ist kommutativ. a+i ist dasselbe wie i+a. Auch bei Pointern und
> Offsets zu Pointern. Daher kann man
>    a[i]    ===>    *( a + i )
>
>                        |
>                        |
>                        v
>
>                    *( i + a )
>
>    i[a]    <======
>
> a[i] ist also dasselbe wie i[a].

Ich frage mich warum sich niemand über diese neckische Rechnung aufregt?

Das Ganze ist zwar formal absolut in Ordnung erweckt aber auch einen 
absolut falschen Eindruck. a und i haben zwar ihre Positionen getauscht 
aber nicht ihre Bedeutung. Bei i[a] ist a noch immer der Pointer auf das 
Array und i der Index. Wäre es anders wäre es ein schlimmer Fehler!

von Karl H. (kbuchegg)


Lesenswert?

Michi schrieb:

> Ich frage mich warum sich niemand über diese neckische Rechnung aufregt?

Wahrscheinlich deshalb, weil alle verstanden haben, worin der springende 
Punkt liegt.

> Das Ganze ist zwar formal absolut in Ordnung erweckt aber auch einen
> absolut falschen Eindruck. a und i haben zwar ihre Positionen getauscht
> aber nicht ihre Bedeutung.

Hab ich auch nicht behauptet.

von Josef (Gast)


Lesenswert?

Vielen Dank euch allen für die guten Beiträge.
Ihr habt mir viel weiter geholfen.

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.