Forum: Compiler & IDEs 32Bit Zahl zusammensetzen


von Jakob (Gast)


Lesenswert?

Guten Abend,

ich möchte 4 8Bit zahlen zu einer 32 Bit Zahl zusammensetzen. Geht dafür 
diese Code?
1
int8_t byte0=0;
2
int8_t byte1=0;
3
int8_t byte2=0;
4
int8_t byte3=0;
5
int32_t value;
6
...
7
value=((((int32_t)byte3 << 24 ) | ((int32_t)byte2 << 16)) | ((int32_t)byte1 << 8)) | byte0;

insbesondere bei der Klammerung und den Casts bin ich mir nicht ganz 
sicher.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Sofern die endianness stimmt, sollte das gehen.

Was hindert Dich daran, es einfach auszuprobieren?

von Rolf Magnus (Gast)


Lesenswert?

Ich würde für Bitschubsereien aber grundsätzlich immer einen 
vorzeichenlosen Typ verwenden.

von Edi R. (edi_r)


Lesenswert?

Ich hab's gerade ausprobiert, weil es mich interessiert hat. Der Code 
von oben wird etwa so übersetzt:
1
  ba:  2f 2f         mov  r18, r31
2
  bc:  33 27         eor  r19, r19
3
  be:  27 fd         sbrc  r18, 7
4
  c0:  30 95         com  r19
5
  c2:  43 2f         mov  r20, r19
6
  c4:  53 2f         mov  r21, r19
7
  c6:  52 2f         mov  r21, r18
8
  c8:  44 27         eor  r20, r20
9
  ca:  33 27         eor  r19, r19
10
  cc:  22 27         eor  r18, r18
11
  ce:  8e 2f         mov  r24, r30
12
  d0:  99 27         eor  r25, r25
13
  d2:  87 fd         sbrc  r24, 7
14
  d4:  90 95         com  r25
15
  d6:  a9 2f         mov  r26, r25
16
  d8:  b9 2f         mov  r27, r25
17
  da:  dc 01         movw  r26, r24
18
  dc:  99 27         eor  r25, r25
19
  de:  88 27         eor  r24, r24
20
  e0:  28 2b         or  r18, r24
21
  e2:  39 2b         or  r19, r25
22
  e4:  4a 2b         or  r20, r26
23
  e6:  5b 2b         or  r21, r27
24
  e8:  86 2f         mov  r24, r22
25
  ea:  99 27         eor  r25, r25
26
  ec:  87 fd         sbrc  r24, 7
27
  ee:  90 95         com  r25
28
  f0:  a9 2f         mov  r26, r25
29
  f2:  b9 2f         mov  r27, r25
30
  f4:  28 2b         or  r18, r24
31
  f6:  39 2b         or  r19, r25
32
  f8:  4a 2b         or  r20, r26
33
  fa:  5b 2b         or  r21, r27
34
  fc:  87 2f         mov  r24, r23
35
  fe:  99 27         eor  r25, r25
36
 100:  87 fd         sbrc  r24, 7
37
 102:  90 95         com  r25
38
 104:  a9 2f         mov  r26, r25
39
 106:  b9 2f         mov  r27, r25
40
 108:  ba 2f         mov  r27, r26
41
 10a:  a9 2f         mov  r26, r25
42
 10c:  98 2f         mov  r25, r24
43
 10e:  88 27         eor  r24, r24
44
 110:  28 2b         or  r18, r24
45
 112:  39 2b         or  r19, r25
46
 114:  4a 2b         or  r20, r26
47
 116:  5b 2b         or  r21, r27

Ziemlich lang. Ich mach das normalerweise ein wenig anders. Es schaut 
zwar auf Anhieb komplizierter aus, ergibt aber einen kompakteren Code:
1
typedef union {
2
  int8_t AsByte[4];  // AsByte[0]..AsByte[3] = LO..HI
3
  int32_t AsLong;
4
} TQuadByte;
5
6
int8_t byte0=0;
7
int8_t byte1=0;
8
int8_t byte2=0;
9
int8_t byte3=0;
10
TQuadByte value;
11
12
...
13
14
value.AsByte[0] = byte0;
15
value.AsByte[1] = byte1;
16
value.AsByte[2] = byte2;
17
value.AsByte[3] = byte3;
18
19
if (value.AsLong == 1234) ....

Das ergibt übersetzt:
1
    value.AsByte[0] = byte0;
2
    value.AsByte[1] = byte1;
3
  ba:  9f 01         movw  r18, r30
4
    value.AsByte[2] = byte2;
5
    value.AsByte[3] = byte3;
6
  bc:  ad 01         movw  r20, r26

Bei meinem Test waren das fast 100 Byte weniger im Flash. Die 
Optimierung war auf -Os eingestellt.

Natürlich kann man das mit dem gleichen Ergebnis auch anders lösen, z. 
B. mit Typecasting, aber das Verschieben halte ich für eine ineffektive 
Lösung. Ein Beispiel für die Typecasting-Lösung:
1
typedef union {
2
  int8_t AsByte[4];  // AsByte[0]..AsByte[3] = LO..HI
3
  int32_t AsLong;
4
} TQuadByte;
5
6
int8_t byte0;
7
int8_t byte1;
8
int8_t byte2;
9
int8_t byte3;
10
int32_t value;
11
12
((TQuadByte)value).AsByte[0] = byte0;
13
((TQuadByte)value).AsByte[1] = byte1;
14
((TQuadByte)value).AsByte[2] = byte2;
15
((TQuadByte)value).AsByte[3] = byte3;
16
17
if (value == 1234) ...

von Rolf Magnus (Gast)


Lesenswert?

Warum müssen eigentlich alle immer die umständlichen und eigentlich 
dafür nicht erlaubten unions verwenden? Übersichtlicher und ganz ohne 
union ginge es so:
1
int8_t byte0=0;
2
int8_t byte1=0;
3
int8_t byte2=0;
4
int8_t byte3=0;
5
int32_t value;
6
7
...
8
9
int8_t* p = (int8_t*)&value;
10
p[0] = byte0;
11
p[1] = byte1;
12
p[2] = byte2;
13
p[3] = byte3;
14
15
if (value == 1234) ....

von Karl H. (kbuchegg)


Lesenswert?

Rolf Magnus schrieb:
> Warum müssen eigentlich alle immer die umständlichen und eigentlich
> dafür nicht erlaubten unions verwenden? Übersichtlicher und ganz ohne
> union ginge es so:

> int8_t* p = (int8_t*)&value;

Hust.
Das ist aber aus C-Standardsicht auch nicht besser :-)

von Jakob (Gast)


Lesenswert?

Und was nimmt man jetzt am besten? Ich bin jetzt total verwirrt :-). Hat 
noch jemand bessere Vorschläge, die Standardkonform und effektv sind?

von Klaus W. (mfgkw)


Lesenswert?

Mach aus deiner ursprünglichen Version eine mit lauter unsigned, und 
alles wird gut.

Erst wenn dir das zu langsam ist (weil die Schieberei nicht
besonders effizient ist), kannst du die union-Variante nehmen
(die von vielen favorisiert wird) oder die letzte mit den Zeigern von
Rolf Magnus (die ich eleganter finde).
Diese beiden sind deutlich schneller, aber sehr unportabel
(weil sie eine bestimmte Endianness voraussetzen, im Gegensatz zur
Schieberei).

von Karl H. (kbuchegg)


Lesenswert?

Wo hast du eigentlich deine
1
int8_t byte0=0;
2
int8_t byte1=0;
3
int8_t byte2=0;
4
int8_t byte3=0;
her? Müssen das 4 einzelne Variablen sein?

Wenn nicht, dann mach doch ein Array draus und caste dir die 
Anfangsadresse des Arrays auf einen uint32_t* um bzw. leg gleich einen 
uint32_t an, und drösel die einzelnen Bytes beim reinschreiben auf.

Effizienter gehts nicht mehr.
Wenn auch nicht Standardkonform und nicht besonders portabel.

von Jakob (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> bzw. leg gleich einen
> uint32_t an, und drösel die einzelnen Bytes beim reinschreiben auf.

Könntest du das bitte etwas genauer erläutern? A besten am Code? Also 
die Bytes werden eingelesen und können auch prinzipiell in ein Array 
rein.

von Karl H. (kbuchegg)


Lesenswert?

1
uint8_t Bytes[4];
2
3
  Bytes[0] = ....    // High Word, High Byte
4
  Bytes[1] = ....    // High Word, Low Byte
5
  Bytes[2] = ....    // Low Word, High Byte
6
  Bytes[3] = ....    // Low Word, Low Byte
7
8
  // mach was mit   (*(uint32_t*)Bytes)  zb
9
  sprintf( Buffer, "Wert war: %ld", (*(uint32_t*)Bytes) );

Das ist natürlich hochgradig davon abhängig, dass die Endianess (also 
die Reihenfolge der Bytes stimmt. Man zwingt den Compiler einfach, die 4 
aufeinanderfolgenden Bytes des Arrays in ihrer Gesamtheit als einen 
uint32_t anzusehen.

Genausogut kann man auch einen uint32_t machen und beim Einlesen das 
gelesene Byte einfach an die richtige Stelle schreiben
1
  uint32_t value;
2
3
  *( (uint8_t*)&value + 0 ) = ..... // High Word, High Byte
4
  *( (uint8_t*)&value + 1 ) = ..... // High Word, Low Byte
5
  *( (uint8_t*)&value + 2 ) = ..... // Low Word, High Byte
6
  *( (uint8_t*)&value + 3 ) = ..... // Low Word, Low Byte
7
8
  // mach was mit  value

man kann auch eine union nutzbringend einsetzen. Man muss ja gar nicht 
kopieren. Es reicht völlig, wenn man den Compiler dazu zwingt die 4 
Bytes als die 4 Bytes einer union aufzufassen. Beim Einlesen liest man 
in die Bytes ein, beim Auslesen holt man sich den uint32_t.
1
union convert
2
{
3
  uint8_t   Bytes[4];
4
  uint32_t  value;
5
};
6
7
union convert;
8
9
  convert.Bytes[0] = .....   // einlesen von 
10
  convert.Bytes[1] = .....
11
  ...
12
13
  // mach was mit convert.value

Streng genommen ist das allerdings nicht erlaubt. Theoretisch darf ein 
Compiler hier alles mögliche machen. Es gibt aber keinen bekannten 
Compiler, bei dem das hier (abgesehen von Endianess) nicht funktioniert 
wie beabsichtigt.
Wenn man es sich nicht leisten kann, eine eigene union für das einlesen 
abzustellen (weil zb der eigentliche buffer größer ist), dann kann man 
immer noch in Analogie zur ersten Variante, ab der Startadresse der 
bewussten 4 Bytes eine union reincasten und so dem Compiler wieder 4 
aufeinanderfolgende Bytes als ein uint32_t unterjubeln :-)


Möglichkeiten gibt es viele.
Die einzige portable und nicht auf irgendwelche Annahmen beasierende ist 
dioe aus dem Originalposting mit zurechtschieben (oder multiplizieren 
der Einzelbytes mit Konstanten) und addieren.

Alle anderen sind Quick&Dirty und hängen hauptsächlich von der Endianess 
der Maschine ab.

Kommt halt immer darauf an, wofür man es braucht und ob es akzeptabel 
ist, hier nicht portabel zu sein.

von Rolf Magnus (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Rolf Magnus schrieb:
>> Warum müssen eigentlich alle immer die umständlichen und eigentlich
>> dafür nicht erlaubten unions verwenden? Übersichtlicher und ganz ohne
>> union ginge es so:
>
>> int8_t* p = (int8_t*)&value;
>
> Hust.
> Das ist aber aus C-Standardsicht auch nicht besser :-)

Doch, ist es, sofern man auf uint8_t umsteigt. Es ist nämlich explizit 
erlaubt, ein beliebiges Objekt als Array aus unsigned char zu 
interpretieren, wogegen es explizit verboten ist, aus einer union ein 
anderes als das zuletzt geschriebene Element zu lesen.
Aber wie gesagt würde ich schon allein aus Gründen der Einfachheit und 
Übersichtlichkeit auf die union verzichten, die hier keinerlei 
zusätzlichen Nutzen bringt.

von Edi R. (edi_r)


Lesenswert?

Auch wenn ich von den C-Experten gesteinigt werde: Ich verwende den GCC 
ausschließlich als AVR-GCC im AVR-Studio, und dort erzeugen die Casts 
mit Union genau den Code, den ich erwarte. Bei jeder anderen Lösung 
(sogar mit dem expliziten Typecast zu einem Array of unsigned char) ist 
der erzeugte Code größer und umständlicher.

Für mich bedeutet das: Ich bleibe bei den Unions, weil für mich ein 
kompaktes Kompilat bei gleichzeitig übersichtlichem Quellcode wichtiger 
ist, als die Einhaltung der C-Standards. Der AVR-GCC enthält 
ausdrücklich Erweiterungen gegenüber dem Standard, z. B. die 
Darstellung von Konstanten als Binärzahl (0b10101010) - warum soll man 
das nicht nutzen? Portabilität ist für mich ein untergeordnetes 
Kriterium, denn von allen Projekten, die ich bisher durchgeführt habe, 
wurde nur ein Einziges auf eine andere Plattform portiert. Bei anderen 
Leuten können die Prioritäten natürlich anders liegen.

Warum ich das schreibe? Als Hinweis für die Leute, die eigentlich nur 
ein wenig basteln wollen. Also: Verwendet einfach die Lösung, die Euch 
am besten zusagt. Es kommt keine C-Polizei und holt Euch ab.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Edi R. schrieb:
> Bei jeder anderen Lösung
> (sogar mit dem expliziten Typecast zu einem Array of unsigned char) ist
> der erzeugte Code größer und umständlicher.

Das wäre auf einem Controller erklärbar, der kein misalignment bei 
32-Bit-Zugriffen zulässt, wie ein ARM, und das auch nur, wenn die 
Anfangsadresse des Arrays eben misalignt ist, aber auf einer 
8-Bit-Quetsche wie einem AVR ist das unerklärlich.

von Edi R. (edi_r)


Lesenswert?

Da hast Du recht.

Wie das Ergebnis bei der Union-Methode ausschaut, habe ich weiter oben 
schon gepostet:

>     value.AsByte[0] = byte0;
>     value.AsByte[1] = byte1;
>   ba:  9f 01         movw  r18, r30
>     value.AsByte[2] = byte2;
>     value.AsByte[3] = byte3;
>   bc:  ad 01         movw  r20, r26

Wobei ich aber zugeben muss, dass das nur dann so kurz wird, wenn byte0 
bis byte3 aufeinanderfolgend im Speicher liegen.

Jetzt habe ich folgendes probiert:
1
uint8_t byte0, byte1, byte2, byte3;
2
uint32_t value;
3
4
...
5
6
*((uint8_t*)&value + 0) = byte0;
7
*((uint8_t*)&value + 1) = byte1;
8
*((uint8_t*)&value + 2) = byte2;
9
*((uint8_t*)&value + 3) = byte3;

(ATtiny45 auf AVR-Studio 4.18.700 mit WinAVR-20100110)
Das Ergebnis:
1
    *((uint8_t*)&value + 0) = byte0;
2
  ce:  f7 01         movw  r30, r14
3
  d0:  60 83         st  Z, r22
4
    *((uint8_t*)&value + 1) = byte1;
5
  d2:  71 83         std  Z+1, r23  ; 0x01
6
    *((uint8_t*)&value + 2) = byte2;
7
  d4:  42 83         std  Z+2, r20  ; 0x02
8
    *((uint8_t*)&value + 3) = byte3;
9
  d6:  53 83         std  Z+3, r21  ; 0x03

So viel länger ist der Code zwar nicht, vor allem nicht so lang wie beim 
Zurechtschieben. Der Compiler übersetzt das als indirektes Schreiben 
(auf was es ja auch herausläuft).

Unübertroffen kurz dürfte dagegen Karl Heinz Bucheggers Lösung mit dem 
Array uint8_t Bytes[4] und dem Casten als *(uint32_t*)Bytes sein.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Rufus t. Firefly schrieb:
> Das wäre auf einem Controller erklärbar, der kein misalignment bei
> 32-Bit-Zugriffen zulässt, wie ein ARM, und das auch nur, wenn die
> Anfangsadresse des Arrays eben misalignt ist, aber auf einer
> 8-Bit-Quetsche wie einem AVR ist das unerklärlich.

Ich glaube kaum, dass der GCC das von alleine mitbekommen würde. Werd's 
gleich mal ausprobieren. Falls er es nicht erkennt, wäre ein 
ineffizienter Zugriff sicherlich das kleinste Problem. Das Array muss 
daher entweder explizit an Wortgrenzen ausgerichtet werden, oder dem 
Compiler muss mitgeteilt werden, dass er hier Vorsicht walten lassen 
möge.

Bei den ARM Kernen die keinen nicht-ausgerichteten Zugriff erlauben 
würde ein solcher bestenfalls zu einer Exception führen (man weiß 
wenigstens woher der Fehler kam). Alternativ würden rotierte Daten 
zurückgeliefert werden, die dem unbedarften Entwickler viele spaßige 
Stunden mit seinem Debugger versprechen.

--
Marcus

von 32Bit (Gast)


Lesenswert?

Hallo,

ich habe gerade etwas ähnliches gemacht.

Ich habe einen Beispielcode bei einer Anwendung dazu bekommen, der wie 
folgt aussieht:

e=a+256*b+65536*c+16777216*d

hier sollte ja a das LowWord LowByte sein.

In C habe ich das nun so geschrieben:
1
  bytes[0] = a;
2
  bytes[1] = b;
3
  bytes[2] = c;
4
  bytes[3] = d;
5
  e=(*(uint32_t*)bytes);

Ist das jetzt nicht genau verkehrt herum? Aber es funktioniert tadellos.

von Klaus W. (mfgkw)


Lesenswert?

Das funktioniert genau so, wenn der Rechner eine little 
endian-Architektur hat.
Bei einem mit big endian müsste es andersrum sein.

von 32Bit (Gast)


Lesenswert?

Also ich habe AVR-GCC genutzt. Wo erfahre ich denn etwas welchen endian 
meine Architektur hat?

von Mark B. (markbrandis)


Lesenswert?

32Bit schrieb:
> Also ich habe AVR-GCC genutzt. Wo erfahre ich denn etwas welchen endian
> meine Architektur hat?

Im Datenblatt?

von 32Bit (Gast)


Lesenswert?

Ok ich hab es gefunden. Aber das ist ja eher vom Compiler abhängig, als 
von der verwendeten Architektur oder?

von Mark B. (markbrandis)


Lesenswert?


von Karl H. (kbuchegg)


Lesenswert?

32Bit schrieb:
> Ok ich hab es gefunden. Aber das ist ja eher vom Compiler abhängig, als
> von der verwendeten Architektur oder?

Es ist von beidem abhängig.
Wenn der Compilerbauer will, kann er auf einer Big Endian Architektur 
auch einen Compiler für Little Endian aufbauen und umgekehrt.

Das wird zwar nicht unbedingt sinnvoll sein, aber möglich ist es.

von Klaus W. (mfgkw)


Lesenswert?

Eher theoretisch möglich.
In aller Regel hat eine Prozessorfamilie entweder BE oder LE
(mit Ausnahme von ARM, die man umstellen kann).

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.