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
typedefunion{
2
int8_tAsByte[4];// AsByte[0]..AsByte[3] = LO..HI
3
int32_tAsLong;
4
}TQuadByte;
5
6
int8_tbyte0=0;
7
int8_tbyte1=0;
8
int8_tbyte2=0;
9
int8_tbyte3=0;
10
TQuadBytevalue;
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:
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:
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 :-)
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).
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.
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.
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_tvalue;
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
unionconvert
2
{
3
uint8_tBytes[4];
4
uint32_tvalue;
5
};
6
7
unionconvert;
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.
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.
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.
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.
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_tbyte0,byte1,byte2,byte3;
2
uint32_tvalue;
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.
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
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.
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.