mikrocontroller.net

Forum: Compiler & IDEs 32Bit Zahl zusammensetzen


Autor: Jakob (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Guten Abend,

ich möchte 4 8Bit zahlen zu einer 32 Bit Zahl zusammensetzen. Geht dafür 
diese Code?
int8_t byte0=0;
int8_t byte1=0;
int8_t byte2=0;
int8_t byte3=0;
int32_t value;
...
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.

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sofern die endianness stimmt, sollte das gehen.

Was hindert Dich daran, es einfach auszuprobieren?

Autor: Rolf Magnus (Gast)
Datum:

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

Autor: Edi R. (edi_r)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich hab's gerade ausprobiert, weil es mich interessiert hat. Der Code 
von oben wird etwa so übersetzt:
  ba:  2f 2f         mov  r18, r31
  bc:  33 27         eor  r19, r19
  be:  27 fd         sbrc  r18, 7
  c0:  30 95         com  r19
  c2:  43 2f         mov  r20, r19
  c4:  53 2f         mov  r21, r19
  c6:  52 2f         mov  r21, r18
  c8:  44 27         eor  r20, r20
  ca:  33 27         eor  r19, r19
  cc:  22 27         eor  r18, r18
  ce:  8e 2f         mov  r24, r30
  d0:  99 27         eor  r25, r25
  d2:  87 fd         sbrc  r24, 7
  d4:  90 95         com  r25
  d6:  a9 2f         mov  r26, r25
  d8:  b9 2f         mov  r27, r25
  da:  dc 01         movw  r26, r24
  dc:  99 27         eor  r25, r25
  de:  88 27         eor  r24, r24
  e0:  28 2b         or  r18, r24
  e2:  39 2b         or  r19, r25
  e4:  4a 2b         or  r20, r26
  e6:  5b 2b         or  r21, r27
  e8:  86 2f         mov  r24, r22
  ea:  99 27         eor  r25, r25
  ec:  87 fd         sbrc  r24, 7
  ee:  90 95         com  r25
  f0:  a9 2f         mov  r26, r25
  f2:  b9 2f         mov  r27, r25
  f4:  28 2b         or  r18, r24
  f6:  39 2b         or  r19, r25
  f8:  4a 2b         or  r20, r26
  fa:  5b 2b         or  r21, r27
  fc:  87 2f         mov  r24, r23
  fe:  99 27         eor  r25, r25
 100:  87 fd         sbrc  r24, 7
 102:  90 95         com  r25
 104:  a9 2f         mov  r26, r25
 106:  b9 2f         mov  r27, r25
 108:  ba 2f         mov  r27, r26
 10a:  a9 2f         mov  r26, r25
 10c:  98 2f         mov  r25, r24
 10e:  88 27         eor  r24, r24
 110:  28 2b         or  r18, r24
 112:  39 2b         or  r19, r25
 114:  4a 2b         or  r20, r26
 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:
typedef union {
  int8_t AsByte[4];  // AsByte[0]..AsByte[3] = LO..HI
  int32_t AsLong;
} TQuadByte;

int8_t byte0=0;
int8_t byte1=0;
int8_t byte2=0;
int8_t byte3=0;
TQuadByte value;

...

value.AsByte[0] = byte0;
value.AsByte[1] = byte1;
value.AsByte[2] = byte2;
value.AsByte[3] = byte3;

if (value.AsLong == 1234) ....

Das ergibt übersetzt:
    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

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:
typedef union {
  int8_t AsByte[4];  // AsByte[0]..AsByte[3] = LO..HI
  int32_t AsLong;
} TQuadByte;

int8_t byte0;
int8_t byte1;
int8_t byte2;
int8_t byte3;
int32_t value;

((TQuadByte)value).AsByte[0] = byte0;
((TQuadByte)value).AsByte[1] = byte1;
((TQuadByte)value).AsByte[2] = byte2;
((TQuadByte)value).AsByte[3] = byte3;

if (value == 1234) ...

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
int8_t byte0=0;
int8_t byte1=0;
int8_t byte2=0;
int8_t byte3=0;
int32_t value;

...

int8_t* p = (int8_t*)&value;
p[0] = byte0;
p[1] = byte1;
p[2] = byte2;
p[3] = byte3;

if (value == 1234) ....

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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 :-)

Autor: Jakob (Gast)
Datum:

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

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht 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).

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wo hast du eigentlich deine
int8_t byte0=0;
int8_t byte1=0;
int8_t byte2=0;
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.

Autor: Jakob (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert

uint8_t Bytes[4];

  Bytes[0] = ....    // High Word, High Byte
  Bytes[1] = ....    // High Word, Low Byte
  Bytes[2] = ....    // Low Word, High Byte
  Bytes[3] = ....    // Low Word, Low Byte

  // mach was mit   (*(uint32_t*)Bytes)  zb
  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
  uint32_t value;

  *( (uint8_t*)&value + 0 ) = ..... // High Word, High Byte
  *( (uint8_t*)&value + 1 ) = ..... // High Word, Low Byte
  *( (uint8_t*)&value + 2 ) = ..... // Low Word, High Byte
  *( (uint8_t*)&value + 3 ) = ..... // Low Word, Low Byte

  // 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.
union convert
{
  uint8_t   Bytes[4];
  uint32_t  value;
};

union convert;

  convert.Bytes[0] = .....   // einlesen von 
  convert.Bytes[1] = .....
  ...

  // 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.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Edi R. (edi_r)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Edi R. (edi_r)
Datum:

Bewertung
0 lesenswert
nicht 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:
uint8_t byte0, byte1, byte2, byte3;
uint32_t value;

...

*((uint8_t*)&value + 0) = byte0;
*((uint8_t*)&value + 1) = byte1;
*((uint8_t*)&value + 2) = byte2;
*((uint8_t*)&value + 3) = byte3;

(ATtiny45 auf AVR-Studio 4.18.700 mit WinAVR-20100110)
Das Ergebnis:
    *((uint8_t*)&value + 0) = byte0;
  ce:  f7 01         movw  r30, r14
  d0:  60 83         st  Z, r22
    *((uint8_t*)&value + 1) = byte1;
  d2:  71 83         std  Z+1, r23  ; 0x01
    *((uint8_t*)&value + 2) = byte2;
  d4:  42 83         std  Z+2, r20  ; 0x02
    *((uint8_t*)&value + 3) = byte3;
  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.

Autor: Marcus Harnisch (mharnisch) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: 32Bit (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
  bytes[0] = a;
  bytes[1] = b;
  bytes[2] = c;
  bytes[3] = d;
  e=(*(uint32_t*)bytes);

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

Autor: Klaus Wachtler (mfgkw)
Datum:

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

Autor: 32Bit (Gast)
Datum:

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

Autor: Mark Brandis (markbrandis)
Datum:

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

Im Datenblatt?

Autor: 32Bit (Gast)
Datum:

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

Autor: Mark Brandis (markbrandis)
Datum:

Bewertung
0 lesenswert
nicht lesenswert

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Klaus Wachtler (mfgkw)
Datum:

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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.