Ist der Compiler hier wirklich so blöd? Ich will eigentlich nur aus 2
Bytes aus dem TWI-Empfang einen 16bit-Wert erzeugen.
Das macht der Compiler draus, Einstellung -Os:
1
bmp_ac1=twi_readack()<<8|twi_readack();// Eeprom Daten auslesen 11 x 2 Byte
2
200:72dfrcall.-284;0xe6<twi_readack>
3
202:c82fmovr28,r24
4
204:70dfrcall.-288;0xe6<twi_readack>
5
206:2c2fmovr18,r28
6
208:30e0ldir19,0x00;0
7
20a:322fmovr19,r18
8
20c:2227eorr18,r18
9
20e:282borr18,r24
10
210:30938900sts0x0089,r19
11
214:20938800sts0x0088,r18
12
bmp_ac2=twi_readack()*256+twi_readack();
13
218:66dfrcall.-308;0xe6<twi_readack>
14
21a:c82fmovr28,r24
15
21c:64dfrcall.-312;0xe6<twi_readack>
16
21e:2c2fmovr18,r28
17
220:30e0ldir19,0x00;0
18
222:322fmovr19,r18
19
224:2227eorr18,r18
20
226:280faddr18,r24
21
228:311dadcr19,r1
22
22a:30937300sts0x0073,r19
23
22e:20937200sts0x0072,r18
So hätte ich das gemacht:
1
bmp_ac1=twi_readack()<<8|twi_readack();// Eeprom Daten auslesen 11 x 2 Byte
2
200:72dfrcall.-284;0xe6<twi_readack>
3
202:sts0x0089,r28
4
204:70dfrcall.-288;0xe6<twi_readack>
5
206:sts0x0088,r28
Kann man dem Compiler beibringen, hier noch etwas intelligenter zu
optimieren?
(avr-gcc im Atmel Studio 7, Atmega8, Compileroption auf -Os, -O2 bringt
gleiches Ergebnis)
Timm T. schrieb:> Kann man dem Compiler beibringen, hier noch etwas intelligenter zu> optimieren?
Gegenfrage:
Wie lange dauern deine beiden I2C-Transfers?
Und wie lange dauern im Vergleich dazu ein paar zusätzliche
Prozessortakte wegen nicht ganz optimalem Code?
Timm T. schrieb:> Ist der Compiler hier wirklich so blöd?
Ja.
union
{
struct { uint8_t b0, b1; } bytewise
uint16_t wordwise;
}
bmp_ac1;
bmp_ac1.bytewise.b0=twi_readack();
bmp_ac1.bytewise.b1=twi_readack();
...bmp_ac1.wordwise;
chris schrieb:> Gegenfrage:> Wie lange dauern deine beiden I2C-Transfers?> Und wie lange dauern im Vergleich dazu ein paar zusätzliche> Prozessortakte wegen nicht ganz optimalem Code?
Darum geht es doch gar nicht. Sondern eher darum, dass jedes mal 6
Befehle (hier 12 Bytes) verschwendet werden.
Seit wann ist das Aufzeigen bzw. Hinterfragen von fragwürdiger bis zu
nicht vorhandener Optimierung verboten? Nur so können Compiler auch
besser werden.
Timm T. schrieb:> So hätte ich das gemacht:
Das Ergebnis des Ausdrucks ist eine 16-Bit Variable. Dementsprechend
wird der Code erzeugt. Dein Code behandelt sie als 2 getrennte 8-Bit
Variablen. Allerdings ist trotzdem recht viel Raum für Verbesserungen.
Optimierung von Operationen auf Teilworte ist nicht die stärkste Seite
von GCC. Ist zwar wesentlich besser geworden (da hat wohl Johann einigen
Anteil), aber perfekt ist das nicht.
Der Compiler weiss möglicherweise nicht, ob die Funktion diese Variable
nutzt. Wäre dies der Fall gäbe es in deinem Code Datensalat.
Unabhängig davon kann es dennoch Datensalat geben, denn in deinem
Ausdruck ist offen, welcher Aufruf zuerst erfolgt. Der linke oder der
rechte.
A. K. schrieb:> Der Compiler weiss möglicherweise> nicht, ob die Funktion diese Variable nutzt.
Das könnte er ja eruieren. Die Funktion ist kein Geheimnis.
A. K. schrieb:> Unabhängig davon kann es dennoch Datensalat geben, denn in deinem> Ausdruck ist undefiniert, welcher Aufruf zuerst erfolgt.
Ja, das macht mir auch Sorge, vor allem wenn es jetzt funktioniert,
heißt das ja nicht, daß das eine andere Compilerversion oder eine andere
Compilereinstellung nicht anders macht.
chris schrieb:> Und wie lange dauern im Vergleich dazu ein paar zusätzliche> Prozessortakte wegen nicht ganz optimalem Code?
Es geht nicht um die Takte, die Zuweisung erfolgt 11 mal, da kommen
einige Byte an sinnlosen Befehlen zusammen.
Michael B. schrieb:> union> {> struct { uint8_t b0, b1; } bytewise> uint16_t wordwise;> }> bmp_ac1;>> bmp_ac1.bytewise.b0=twi_readack();> bmp_ac1.bytewise.b1=twi_readack();> ...bmp_ac1.wordwise;
Danke, die Logik dahinter ist mir verständlich, aber das schreit
irgendwie nach: Dann kann ichs auch gleich wieder in ASM machen.
Timm T. schrieb:> Dann kann ichs auch gleich wieder in ASM machen.
Wenn du stets erwartest, dass der Compiler es genauso tut wie du in
Assembler, dann solltest du keinen Compiler verwenden, sondern
Assembler. Das schont deine Nerven.
Von grossen Maschinen stammende Compiler wie GCC gehen in weiten Teilen
davon aus, dass die Maschine Wortoperationen beherrscht, also
Operationen auf int/unsigned Grundoperationen der Maschine sind. Im
Prinzip ist C so definiert.
Bei fast allen Zielmaschinen ist der auch der Fall, bei AVR nicht.
Optimierungen, die wie hier auf effektiv kombinierte Teilwortoperationen
abzielen, werden dir deshalb immer irgendwo fehlen.
Datentypen: Die twi_read geben uint_8 zurück, die bmp_xx sind uint_16
oder int_16, macht aber keinen Unterschied im Compilat.
Ich werds wahrscheinlich in eine twi_readword-Funktion packen, dann ist
die Reihenfolge auch eindeutig.
Michael B. schrieb:> union> {> struct { uint8_t b0, b1; } bytewise> uint16_t wordwise;> }> bmp_ac1;
Meine Idee war, das in ein Bytearray zu mappen, dann könnte ich das in
einem Rutsch mit einer Schleife auslesen.
Ich brauch aber in den nachfolgenden Berechnungen (Bosch Drucksensor
BMP180) die einzelnen Variablen.
Das müßte dann so aussehen:
Timm T. schrieb:> Ist der Compiler hier wirklich so blöd?
Welcher denn eigentlich?
Und nein, bitte nicht „GCC“ als Antwort, sondern die genaue Version
des Compilers sowie die benutzten Optionen.
Timm T. schrieb:> Es geht nicht um die Takte, die Zuweisung erfolgt 11 mal, da kommen> einige Byte an sinnlosen Befehlen zusammen.
Dann bist Du selber der noch größere Codeverschwender, weil Du keine
Schleife draus machst.
Gegen Copy&Paste Verschwendung kann auch der beste Compiler nichts
ausrichten.
Schon wenn Sachen zweimal auftreten, überlege ich, ob ich ne Schleife
oder wenigstens ne Funktion draus mache. Das reduziert auch die
Fehlermöglichkeiten, da dann ein Fehler nur an einer Stelle gemacht
werden kann.
Wenn ich schon den Titel sehe, kommt mir der Kaffee hoch.
Einfach mal ein paar Monate probieren einen eigenen Compiler zu basteln
und dann ermässigt sich die empörte Frage auf ein: "Wünsche mir Patch
für Optimierung im Falle der Kombination zweier Funktionsresulate" oder
so ähnlich.
Dann noch die Frage im zu dem Compiler passenden Forum posten oder auch
als "Improvement request".
Denn die Frage ob das geht, lässt sich allenfalls bejahen oder verneinen
und das reicht dem TO sicher nicht.
So ist das reine Frust-Abladerei. Und sind wir hier die Mülltonnen für
anderer Leute Frustration? Nein!
Meckern ist leicht. Besser machen dann schon nicht mehr.
A. K. schrieb:> Lokale Variablen sind meist effizienter als globale.
Ich muß aber die Kalibrierdaten am Anfang einlesen und dann immer wieder
damit rechnen. Da hätte ich jetzt keine Idee, wie ich die lokal
verarbeiten soll, außer sie jedesmal neu einzulesen.
Jörg W. schrieb:> Welcher denn eigentlich?
Invoking: AVR/GNU C Compiler : 4.9.2
"C:\Program Files
(x86)\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\bin\avr-gcc.exe
" -x c -funsigned-char -funsigned-bitfields -DDEBUG -I"C:\Program
Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.0.90\include" -Os
-ffunction-sections -fdata-sections -fpack-struct -fshort-enums -g2
-Wall -mmcu=atmega8 -B "C:\Program Files
(x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.0.90\gcc\dev\atmega8" -c
-std=gnu99 -MD -MP -MF "gh_main.d" -MT"gh_main.d" -MT"gh_main.o" -o
"gh_main.o" ".././gh_main.c"
Invoking: AVR/GNU Linker : 4.9.2
"C:\Program Files
(x86)\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\bin\avr-gcc.exe
" -o gh.elf gh_main.o -Wl,-Map="gh.map" -Wl,--start-group -Wl,-lm
-Wl,--end-group -Wl,--gc-sections -mmcu=atmega8 -B "C:\Program Files
(x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.0.90\gcc\dev\atmega8"
Staubfänger schrieb:> Wenn ich schon den Titel sehe, kommt mir der Kaffee hoch.
Um diese Uhrzeit? ;-)
> So ist das reine Frust-Abladerei.
Ja. Aber dafür wiederum solltest du die Nerven haben, es ggf. zu
ignorieren.
Das Problem scheint zu sein, dass der GCC ungern/nie Variablen im Ram
manipuliert ohne sie komplett in die Register zu lesen.
So führen solche Konstrukte, die eigentlich genau das bezwecken sollen,
zu schlimmen Code:
1
out=out&0xff00|twi_readack();
1
80: c0 91 00 01 lds r28, 0x0100
2
84: d0 91 01 01 lds r29, 0x0101
3
88: cc 27 eor r28, r28
4
8a: e5 df rcall .-54 ; 0x56 <twi_readack>
5
8c: c8 2b or r28, r24
6
8e: d0 93 01 01 sts 0x0101, r29
7
92: c0 93 00 01 sts 0x0100, r28
Auch ein einfaches
1
out&=0xff;
führt zu
1
2
bc: 89 81 ldd r24, Y+1 ; 0x01
3
be: 9a 81 ldd r25, Y+2 ; 0x02
4
c0: 99 27 eor r25, r25
5
c2: 9a 83 std Y+2, r25 ; 0x02
6
c4: 89 83 std Y+1, r24 ; 0x01
Am besten scheint die Variante mit temporären Variable zu sein. Damit
umgeht man das Problem, aber der Code wird auch ein Stück ineffizienter
als möglich:
1
uint16_t temp;
2
temp = twi_readack();
3
5e: fb df rcall .-10 ; 0x56 <twi_readack>
4
60: c8 2f mov r28, r24
5
62: d0 e0 ldi r29, 0x00 ; 0
6
temp |= twi_readack() << 8;
7
64: f8 df rcall .-16 ; 0x56 <twi_readack>
8
66: d8 2b or r29, r24
9
out = temp;
10
68: d0 93 01 01 sts 0x0101, r29
11
6c: c0 93 00 01 sts 0x0100, r28
Asm-Code kommt hier von AVR/GNU C Compiler : 4.8.1
Staubfänger schrieb:> Meckern ist leicht. Besser machen dann schon nicht mehr.
Und du meckerst hier am meisten. Der Beitrag von TO ist um
Größenordnungen sinnvoller als dein Erguss.
Timm T. schrieb:> Da hätte ich jetzt keine Idee, wie ich die lokal> verarbeiten soll, außer sie jedesmal neu einzulesen.
Ich kenne deinen Code nicht. Das war nur ein allgemeiner Hinweis.
Staubfänger schrieb:> Wenn ich schon den Titel sehe, kommt mir der Kaffee hoch.
Ich zitiere Dich dann, wenn es im Forum wieder mal heißt: C ist ja sooo
viel besser als ASM.
Weißt Du, da kommt MIR der Kaffee hoch. Ich wüßte sofort, wie ich es in
ASM machen müßte und hätte es schon fertig. Aber ich bin ja lernwillig,
und ich bin auch bereit mal über den Tellerrand zu schauen, und mal was
Neues auszuprobieren. Aber wenn ich dann ne Frage hab: Ist das normal
so, oder geht das noch besser? werd ich blöd angemacht.
Peter D. schrieb:> Dann bist Du selber der noch größere Codeverschwender, weil Du keine> Schleife draus machst.
Ist ja nicht so, daß ich die Idee nicht auch schon hatte. Und wie
bekomme ich die Werte aus der Schleife dann in 11 verschiedene
Variablen?
Wie gesagt, ich würde es in ein Bytearray schreiben, und die einzelnen
Bytes jeweils wordweise auf die Variablen mappen. Also in ASM würde ich
das so machen. In C weiß ich nicht, ob und wie das geht.
Timm T. schrieb:> Invoking: AVR/GNU C Compiler : 4.9.2
OK, zumindest erstmal nicht hornalt. ;-)
Ansonsten das übliche Blahblah an Atmel-Studio-generierten Optionen,
teilweise zweifelhaft (-funsigned-char), teilweise unsinnig
(-fpack-struct, hält sich ungemein hartnäckig, obwohl es bei einer
8-Bit-Architektur absolut unnütz ist, denn die werden immer gepackt).
avr schrieb:> Am besten scheint die Variante mit temporären Variable zu sein.
Ja, temporäre uint8_t-Variable verhelfen dem Compiler häufig zur
klareren Erkenntnis, dass die vorgeschriebenen Regeln für die integer
promotion hier gar nicht nötig sind. Bei komplexeren Ausdrücken
scheint der Compiler schneller den Überblick zu verlieren, in welchen
Fällen die 16 Bits tatsächlich nötig sind.
avr schrieb:> Das Problem scheint zu sein, dass der GCC ungern/nie Variablen im Ram> manipuliert ohne sie komplett in die Register zu lesen.
Im Zwischencode und in weiten Teilen der Optimierung sind das
Wortoperationen, keine Byteoperationen. Zu Bytebefehlen werden sie erst
recht spät in der Erzeugung von AVR Code, oft recht schematisch, weshalb
man - wie hier - die 16-Bit Operationen erkennen kann:
200: 72 df rcall .-284 ; 0xe6 <twi_readack>
202: c8 2f mov r28, r24
temp1_8bit = ...
204: 70 df rcall .-288 ; 0xe6 <twi_readack>
temp2_8bit = ...
206: 2c 2f mov r18, r28
208: 30 e0 ldi r19, 0x00 ; 0
temp3_16bit = extend temp1_8bit
20a: 32 2f mov r19, r18
20c: 22 27 eor r18, r18
temp4_16bit = temp3_16bit * 256
20e: 28 2b or r18, r24
temp5_16bit = temp4_16bit | ...
Hier ist die einzige Optimierung sichtbar.
avr schrieb:
[...]
> Staubfänger schrieb:>> Meckern ist leicht. Besser machen dann schon nicht mehr.>> Und du meckerst hier am meisten. Der Beitrag von TO ist um> Größenordnungen sinnvoller als dein Erguss.
Das kann man so oder so sehen.
Eine sachliche Darstellung des Problems leidet doch sehr, wenn sie mit
einer unsachlichen Qualifizierung eingeleitet wird. Ausserdem fällt mir
der TO nicht zum ersten Mal negativ auf.
Aber danke für Deine Mitteilung.
Timm T. schrieb:> In C weiß ich nicht, ob und wie das geht.
Wurde dir ja schon gezeigt: mit einer union.
Ist halt nicht portabel, aber das wäre deine Assemblerlösung auch
nicht, folglich muss eine C-Lösung es ebenfalls nicht sein.
Ansonsten: gib' uns compilierfähigen Code, dann können wir dir bessere
Ratschläge erteilen. ;)
Timm T. schrieb:> Ich zitiere Dich dann, wenn es im Forum wieder mal heißt: C ist ja sooo> viel besser als ASM.
Dann tu dich mit unserem bekannten ASM-Fan zusammen und lass C sein.
Oder akzeptiere, dass ein kostenloser Compiler oft nicht jenen Code
erzeugen wird, den du gerne sehen willst. Oder nur nach aufwändigem
tweaking.
Vielen Leuten kommt es nicht darauf an, das letzte Byte Code
einzusparen, sondern Entwicklungszeit einzusparen. Wenn dabei etwas mehr
Code rauskommt als in Assembler, dann ist das eben so.
Jörg W. schrieb:> Wurde dir ja schon gezeigt: mit einer union.
Mit einer union von Bytes und Words. Aber geht das auch auf ein Array?
Das interessiert mich prinzipiell, weil ich das schon mehrfach gebraucht
hätte, aber in den Tuts sind nur die einfachen unions zwischen Variablen
erklärt.
Oder könnte man die Variablen zu einer Structure zusammenfassen und die
Structure auf ein Array pappen?
Die Idee ist prinzipiell: Lese alle Daten in einem Rutsch mit einer
Schleife in das Array, greife als Variable auf das Array zu.
Timm T. schrieb:> Oder könnte man die Variablen zu einer Structure zusammenfassen und die> Structure auf ein Array pappen?
Grundprinzip:
union {
uint8_t bytes[12];
uint16_t words[6];
};
Timm T. schrieb:> Mit einer union von Bytes und Words. Aber geht das auch auf ein Array?
Klar kannst du auch ein Array von Unions anlegen:
1
structb8_i16{
2
uint8_tb[2];
3
uint16_ti;
4
};
5
6
structb8_i16array[42];
OK, aber dann müsstest du die beiden Bytes eines 16-Bit-Halbwortes
natürlich immer noch einzeln adressieren (b[0] und b[1]).
Was natürlich auch geht, nicht mehr, aber auch nicht weniger portabel:
1
structarray{
2
uint8_tb[42];
3
uint16_ti[21];
4
};
5
6
structarraya;
Nachteil: die Gesamtgröße ist schon im Datentyp codiert, du kannst
den also nur genau für ein Array dieser Größe benutzen. Wenn du
40 Bytes (20 Halbworte) brauchst, musst du einen neuen Typen anlegen.
A. K. schrieb:> Dann tu dich mit unserem bekannten ASM-Fan zusammen und lass C sein.
Ja sorry, daß das C-Implantat bei mir leider vergessen wurde. Ich weiß,
manche Leute können C schon von Geburt an und müssen nichts mehr lernen.
A. K. schrieb:> Vielen Leuten kommt es nicht darauf an, das letzte Byte Code> einzusparen, sondern Entwicklungszeit einzusparen. Wenn dabei etwas mehr> Code rauskommt als in Assembler, dann ist das eben so.
Mir kommt es auch nicht auf das letzte Byte an. Mir geht es darum, etwas
zu lernen. Vielleicht gibt es einfach eine bessere Möglichkeit, die
Aufgabe zu formulieren, bei der der dumme Compiler effizienter arbeitet.
Du mußt hier nicht die Ehre der Maschinen verteidigen, das machen die in
Kürze sowieso selber.
Timm T. schrieb:> So hätte ich das gemacht:> bmp_ac1 = twi_readack() << 8 | twi_readack(); // Eeprom Daten> auslesen 11 x 2 Byte> 200: 72 df rcall .-284 ; 0xe6 <twi_readack>> 202: sts 0x0089, r28> 204: 70 df rcall .-288 ; 0xe6 <twi_readack>> 206: sts 0x0088, r28
Und doppelt falsche Werte reingeschrieben.
avr schrieb:> Darum geht es doch gar nicht. Sondern eher darum, dass jedes mal 6> Befehle (hier 12 Bytes) verschwendet werden.
Dann mach doch eine Integer-Funktion daraus.
Deine (falsche) Assembler-Routine braucht 12 Bytes, Compiler braucht
12 Bytes mehr, das sind aber nur 6 zusätzliche Takte.
Und eine Rechenoperation, die man mehr als zweimal benutzt, sollte man
sowieso als Funktion schreiben.
Portable Alternative:
uint16_t words[...];
uint8_t *bytes = (uint8_t *)words;
Nach bytes[...] einlesen und als words[...] verwenden.
Geht auch mit einer struct statt words[]. Zur Wahrung der Portabilität
ist nur erforderlich, dass über einen char-Pointer eingelesen wird, weil
die eine gewisse Sonderrolle in C haben.
Timm T. schrieb:> Ja sorry, daß das C-Implantat bei mir leider vergessen wurde. Ich weiß,> manche Leute können C schon von Geburt an und müssen nichts mehr lernen.
Nein. Aber mit über 3 Jahrzehnten C im Gepäck und 1-2 teilweise
geschriebenen C Compilern habe ich einen gewissen Vorsprung. Nur
solltest du nicht erwarten, den in Nullkommanix einzuholen. ;-)
> Mir kommt es auch nicht auf das letzte Byte an. Mir geht es darum, etwas> zu lernen.
Eigentlich kam hauptsächlich rüber, dass du deinen Frust irgendwie
loswerden musstest. Das führt nicht immer zu den hilfreichsten
Antworten.
Also mal der konkrete Fall für den BMP180, kann man ja dann auf andere
Sachen übertragen. Die Parameter stehen so da:
1
Calibrationcoefficients
2
ParameterMSBLSB
3
AC10xAA0xABsigned
4
AC20xAC0xADsigned
5
AC30xAE0xAFsigned
6
AC40xB00xB1unsigned
7
AC50xB20xB3unsigned
8
AC60xB40xB5unsigned
9
B10xB60xB7signed
10
B20xB80xB9signed
11
MB0xBA0xBBsigned
12
MC0xBC0xBDsigned
13
MD0xBE0xBFsigned
Ich lese die Bytes von 0xAA bis 0xBF aus und schreibe sie in ein Array.
Ich greife dann über die Variablen AC1 bis MD auf die Words im Array zu.
Geht dann sowas?
A. K. schrieb:> Besser, als Prinzip zum selber ausfüllen:>
1
union{
2
>uint8_tdaten[11*2];
3
>struct{
4
>uint16_tAC1,AC2,AC3
5
>int16_tAC4...
6
>uint16_t...MD;
7
>};
8
>};
Ah, siehste, das sind die Feinheiten.
Wie spreche ich die dann an? Einfach mit daten[i] und AC1... geht das in
einer Structure? Muß da nicht immer sowas wie xxx.AC1 stehen?
Bleibt noch das Problem, daß die Daten aus dem BMP180 mit MSB, LSB
kommen, in uint16 aber LSB, MSB stehen, also lowbyte und highbyte
vertauscht sind. Sehe ich das richtig?
Timm T. schrieb:> Wie spreche ich die dann an? Einfach mit daten[i] und AC1... geht das in> einer Structure? Muß da nicht immer sowas wie xxx.AC1 stehen?
An der Stelle wär dann doch mal das RTFM fällig. Also der Tipp, es mal
mit einem C Handbuch zu versuchen. Da steht drin, wie man mit unions und
structs arbeitet.
> Bleibt noch das Problem, daß die Daten aus dem BMP180 mit MSB, LSB> kommen, in uint16 aber LSB, MSB stehen, also lowbyte und highbyte> vertauscht sind. Sehe ich das richtig?
Siehst du richtig, Aber niemand zwingt dich dazu, die in exakt
sequentieller Reihenfolge abzuspeichern:
for (uint8_t i = 0; i < N; ++i)
bytes[i^1] = read_byte();
Timm T. schrieb:> Aber wenn ich dann ne Frage hab: Ist das normal so, oder geht das noch> besser? werd ich blöd angemacht.
Du wirst nicht böd angemacht, weil du die Frage gestellt hast, sondern
weil du sie mit den Worten "Blöder Compiler" begonnen und mit "Ist der
Compiler hier wirklich so blöd?" fortgeführt hast. Wenn man das tut,
muss man eben auf entsprechenden Gegenwind gefasst sein.
A. K. schrieb:> Portable Alternative:>> uint16_t words[...];> uint8_t *bytes = (uint8_t *)words;>> Nach bytes[...] einlesen und als words[...] verwenden.
Gefällt mir auch besser als die unions.
Ich staune immer bei einfachen ISR, wie viel Code der Compiler da
erzeugt.
Im Groben und Ganzen bin ich mit dem GCC jedoch zufrieden. In C
programmieren ist doch wesentlich einfacher, als in Assembler.
Da nehme ich die kleinen Nachteile gerne in Kauf. Auf der Arbeit spielt
sich das Ganze noch ein Level höher ab, bei Java versus C++. Ich finde
Java ziemlich doof, aber Geld verdienen kann man damit letztendlich
trotzdem gut.
Stefan U. schrieb:> Ich staune immer bei einfachen ISR, wie viel Code der Compiler da> erzeugt.
Das liegt nicht zuletzt am Aufwand am Anfang/Ende der ISR. Die AVR
Architektur ist recht RISC-mässig raumgreifend definiert. Eine
Akku-Architektur hat es einfacher, weil kaum was gesichert werden muss.
Und die Cortex M sind an dieser Stelle speziell optimiert worden.
Moin,
ein union zu benutzen ist keine gute Idee. Das erzeugt gerne mal
undefined behaviour (padding bytes und so). Mit uint8_t sollte es
natürlich funktionieren, aber man muss sich so was ja nicht angewöhnen.
Übrigens:
Timm T. schrieb:> union {> uint8_t daten[11 * 2];> uint16_t AC1, AC2, AC3> int16_t AC4 ...> uint16_t ... MD;> }
Nein, da liegen AC1 und AC4 ja an derselben Speicherstelle.
Lies halt mal im C-Grundlagenbuch was Unions sind.
union
{
uint8_t daten[11 * 2];
struct
{
uint16_t AC1, AC2, AC3
int16_t AC4 ...
uint16_t ... MD;
}
params;
}
nicht"Gast" schrieb:> ein union zu benutzen ist keine gute Idee.
Ich sehe es genau andersrum.
Beim Cast weiß ich nicht, wie die Byte-Order ist.
Bei der Union muß ich das nur einmal prüfen und dann ist es eindeutig.
Ich kann sogar die Union für verschiedene Compiler umdefinieren.
Z.B. funktioniert dann die Union auf dem 8051 (C51) und auf dem AVR
(AVR-GCC). Der Cast würde gewaltig in die Hose gehen.
Alle Variabeln, die in einem union stehen, beginnen an der selben
Adresse.
Wenn du nun ein
uint16_t a
int16_t b
float c
schreibst, greifst du bei allen auf die gleichen 16 Bits zu, werden aber
verschieden interpretiert.
In deinem Fall teilen sich die Variablen daten und params den gleichen
Adressbereich. Das Union hilft lediglich dem Programmierer mit dem
"aliasen", um es logisch lesbarer zu halten.
Peter D. schrieb:> Ich sehe es genau andersrum.> Beim Cast weiß ich nicht, wie die Byte-Order ist.
Ich verstehe nicht was du meinst. Ist doch genau der gleiche Effekt.
Schwierig ist das bei "struct", wenn man darin Bytes und Nicht-Bytes
mischt. Dann muss man bei den Zugriffen auf die Nicht-Bytes eigentlich
stets sowas einfliessen lassen, wie bei TCP/IP mit ntohs(). Und hat
Zirkus mit Alignment.
Beim obigen BMP ist es einfacher, weil nur 16-Bit Worte. Weshalb auf die
Variante mit "uint16_t words[];" statt struct den Vorteil hat, darauf
direkt hinzuweisen. Dann kann man mit dem vorhin gezeigten [i^1] die
Daten passend reinlegen und fertig.
A. K. schrieb:> Ich verstehe nicht was du meinst. Ist doch genau der gleiche Effekt.
Nö, ich definiere die Union einmalig je nach Compiler und brauche mich
fürderhin nicht mehr darum zu kümmern:
nicht"Gast" schrieb:> Das erzeugt gerne mal undefined behaviour (padding bytes und so).
Padding ist nicht undefined, sonder implementation-defined (soweit
ich mich erinnere). Für den AVR wiederum mit seiner 8-bit-Architektur
definiert die Implementierung, dass nie Padding notwendig ist.
Padding ist ja kein Selbstzweck, um künstlich Speicher zu verplempern,
sondern Mittel zum Zweck auf Architekturen, die für bestimmte Daten
eine spezifische Ausrichtung im Speicher benötigen. Ein 8-Bitter
braucht das nie.
Man kann ansonsten auch Datenstrukturen so definieren, dass sie
Padding-Probleme von vornherein berücksichtigen. Das Prinzip ist
dabei „vom Großen zum Kleinen“:
1
structfoo{
2
uint32_ta;// passt immer
3
uint16_tb;// passt auch ohne Padding
4
uint8_tc;// passt ebenfalls
5
// Hier am Ende wäre auf einer 16- oder 32-Bit-Architektur noch
6
// ein Byte Padding nötig, kann man natürlich auch gleich selbst
7
// reindefinieren:
8
uint8_tdummy;
9
};
Dass das Ganze in so einer einfachen Form nicht portabel ist, ist
sonnenklar, aber viele einfache Aufgaben auf einem AVR muss man oft
auch gar nicht portabel implementieren. Wie A. K. schon schrieb,
treibt man sonst immer gleich einen Rattenschwanz an Mehrarbeit, weil
man nicht nur Padding, sondern auch die Bytereihenfolge berücksichtigen
muss.
Jörg W. schrieb:> auch gar nicht portabel implementieren. Wie A. K. schon schrieb,> treibt man sonst immer gleich einen Rattenschwanz an Mehrarbeit, weil> man nicht nur Padding, sondern auch die Bytereihenfolge berücksichtigen> muss.
Ich denke, peda und nicht A.K. hat das mit Endian geklärt ?
Marc V. schrieb:> Jörg W. schrieb:>> auch gar nicht portabel implementieren. Wie A. K. schon schrieb,>> treibt man sonst immer gleich einen Rattenschwanz an Mehrarbeit, weil>> man nicht nur Padding, sondern auch die Bytereihenfolge berücksichtigen>> muss.>> Ich denke, peda und nicht A.K. hat das mit Endian geklärt ?Beitrag "Re: Blöder Compiler - Optimieren in C?"
Jörg W. schrieb:> Man kann ansonsten auch Datenstrukturen so definieren, dass sie> Padding-Probleme von vornherein berücksichtigen.
Genau. Das hat man auch schon in den alten Zeiten gemacht als man solche
Dinge wie TCP-Header und dergleichen definierte, die sind alle so
angelegt daß jedes Element auf einem Offset sitzt der durch seine eigene
Größe teilbar ist und sich somit immer ganz ohne Padding zusammenfügen
lassen, das war kein Zufall, das war weise Voraussicht.
Wenn ich mir ein Struct ausdenke für irgendwelche Konfigurationsdaten
lasse ich bei der Deklaration erstmal alle 4 Bytes eine Leerzeile, so
kann ich ohne viel abzuzählen sofort sehen an welcher Stelle ich noch
ein int8 oder ein int16 unterbringen kann und wo noch Lücken sind die
ich mit dummy-bytes auffüllen muss oder durch umsortieren ans Ende
befördern kann.
> Dass das Ganze in so einer einfachen Form nicht portabel ist,
Das würde ich nicht so dramatisch sehen. Die meisten (alle?) heute und
auch damals gängigen ABIs verweden für alle Typen ein Alignment das
genau der Größe des jeweiligen Typs entspricht (self-aligned), also die
Speicheradresse muss durch die Größe des Typs teilbar sein. So kann man
ein Struct lückenlos füllen wenn man alles richtig anordnet.
Zur Sicherheit kann man noch durch ein compile-time assert absichern daß
die Gesamtgröße des structs genau dem entspricht was man erwartet hat
und wenn man dann in 100 Jahren mal zufällig einen Prozessor begegnet
bei dem es keine self-aligned typen mehr gibt knallt es sofort beim
compilieren und man kann sich dann immer noch überlegen wie man das
anders löst.
Bernd K. schrieb:> Genau. Das hat man auch schon in den alten Zeiten gemacht als man solche> Dinge wie TCP-Header und dergleichen definierte, die sind alle so> angelegt daß jedes Element auf einem Offset sitzt der durch seine eigene> Größe teilbar ist und sich somit immer ganz ohne Padding zusammenfügen> lassen, das war kein Zufall, das war weise Voraussicht.
Nur hatte man diese Voraussicht vermissen lassen, als man TCP/IP in
Ethernet einpackte. Denn da ist Essig mit dem Alignment vom TCP/IP, wenn
der Ethernet-Frame aligned ist. Weshalb manche den Ethernet-Frame
gezielt misalignen, damit TCP/IP wieder passt.
Jörg W. schrieb:> Beitrag "Re: Blöder Compiler - Optimieren in C?"
Alignment war damit nicht gemeint.
Der TO liest Bytes in Big-Endian, schreibt diese aber in
Little-Endian.
Das hat peda mit Cast und 'gewaltig in die Hose gehen' gemeint.
Marc V. schrieb:> Jörg W. schrieb:>> Richtig. ntohs() (etc.) kümmert sich um die Byteorder.>> Bei GCC ?
Weißt du überhaupt, was noths(), htons(), ntohl() und htonl() sind?
Jörg W. schrieb:> Weißt du überhaupt, was noths(), htons(), ntohl() und htonl() sind?
So ungefähr.
Nur kann ich die beim GCC nicht finden.
Besonders noths().
Marc V. schrieb:> Nur kann ich die beim GCC nicht finden.
Die stammen ja auch aus dem IP-Stack (welchen auch immer du nimmst).
Der Compiler selbst hat sowas nicht.
Allerdings haben neuere Intel-CPUs, wenn ich mich recht entsinne, das
notwendige Pendant dazu bereits in Hardware, für diese wird der
Compiler sicher ein builtin anbieten.
Jörg W. schrieb:> notwendige Pendant dazu bereits in Hardware, für diese wird der> Compiler sicher ein builtin anbieten.
gcc 4.7.2 interessanterweise nicht (known bug). Für 32 Bits ja, für 16
Bits nein. Hardware für 16 Bits gibts seit Anbeginn der x86 Ära, für 32
Bits seit 486.
A. K. schrieb:>> Besonders noths().>> Weil Teil vom Socket API. Mit TCP/IP Lib stehen die Chancen besser.
Es war ein Shreibfehler von Jörg W. und es war nicht ernst gemeint.
Mit einem einfachem #define my_ntohs(x) swappt der Compiler einfach
die Bytes, bzw. nimmt zuerst das Byte aus der höherwertigen Adresse
ohne die ganze bitschieberei die in #define steht.
Also ist die ganze Diskussion ob der Compiler blöd ist oder nicht,
überflussig.