Hallo zusammen, ich frag mich grad wie der Compiler (Avrstudio5, avr-gcc) Integer-Werte unterschiedlicher Dimension verarbeitet. Derzeit caste ich alles auf die maximale Größe. Beispiel: #define TIMER_FREQ (uint16_t) 32768 volatile uint16_t v16 = 0; volatile uint32_t v32 = 0; ... v16 = [100...1023]; v32 = 256*60*TIMER_FREQ*10 / (v16*4*2); Anmerkung: 256*60*TIMER_FREQ*10 überschreitet uint_32_t Ich hoffe, dass der Compiler die Konstanten in Zähler/Nenner zuverlässig selber rauskürzt und die Rechnung auch zuverlässig automatisch auf 32-Bit erweitert. Hab aber das ungute Gefühl, dass da manchmal/immer auf 8/16-Bit abgeschnitten wird. Was muss wirklich sein (casts) und was ist überflüssig? Auch für künftige Compilerversionen (ohne Bugs). Best practice? Stephan
:
Verschoben durch Moderator
Stephan schrieb: > Hallo zusammen, > > ich frag mich grad wie der Compiler (Avrstudio5, avr-gcc) Integer-Werte > unterschiedlicher Dimension verarbeitet. Genau so, wie es die C-Regeln vorschreiben. > Ich hoffe, dass der Compiler die Konstanten in Zähler/Nenner zuverlässig > selber rauskürzt Wenn das den Unterschied zwischen einem Overflow und keinem Overflow ausmacht, dann hat der Compiler einen Fehler gemacht. Denn nach einer derartigen Optimierung muss EXAKT das gleiche rauskommen (innerhalb dessen, was durch die Sprache definiert wird), wie ohne Optimierung. Und das beinhaltet auch Overflows. > Was muss wirklich sein (casts) Geh deine Berechnung durch, so wie sie abgearbeitet wird. Setze deine Zahlenwerte ein und sieh nach welcher Teilschritt überläuft. Edit: Allerdings kann das Umstellen einer Formel durch den Programmierer oft den Unterschied zwischen Overflow und keinem Overflow ausmachen.
Karl Heinz Buchegger schrieb: > Stephan schrieb: >> Ich hoffe, dass der Compiler die Konstanten in Zähler/Nenner zuverlässig >> selber rauskürzt > > Wenn das den Unterschied zwischen einem Overflow und keinem Overflow > ausmacht, dann hat der Compiler einen Fehler gemacht. > Denn nach einer derartigen Optimierung muss EXAKT das gleiche rauskommen > (innerhalb dessen, was durch die Sprache definiert wird), wie ohne > Optimierung. Und das beinhaltet auch Overflows. Da Integer-Overflows undefiniertes Verhalten darstellen, kann der Compiler bei der Optimierung einfach so tun, als ob der Überlauf nicht stattfindet. Es darf also was anderes (oder sogar beliebiges) dabei herauskommen. Z.b. macht gcc aus sowas:
1 | int wrap(int a) { |
2 | return (a + 1 > a); |
3 | }
|
Das hier:
1 | int wrap(int a) { |
2 | return 1; |
3 | }
|
wenn Optimierung >=O2 oder Os an ist, weil der Optimierer dann so tut, als könne a nicht überlaufen. (die Option -fstrict-overflow ist dann an) Die Rechnung des OP findet aber tatsächlich in unsigned statt, wo es keinen Überlauf sondern nur wrap-around gibt, da es sich um Modulo-Arithmetik handelt. Hier kann der Compiler nicht automatisch kürzen wie man es ansonsten erwarten würde, weil die Kürzungsregeln für Kongruenzen völlig anders sind. Wenn ich modulo 10 rechne, dann ist 2x5/5 nicht 2 sondern 0, da man der Ausdruck (2x5)/5 enspricht und 2x5 == 0 mod 10 ist. Jetzt ist aber TIMER_FREQ ein uint16_t. Da bei avr-gcc die ints ebenfalls 16 Bit sind, wird bei der Auswertung von '256*60*TIMER_FREQ*10' der Teilausdruck (256*60) (der int ist) bei der Multiplikation mit TIMER_FREQ in uint16_t konvertiert. Im Grunde wird also 250u*60u*32768u*10u ausgerechnet, was mod 2^16 aber 0 ist: 250u*60u*32768u*10u => 250u*60u*5u*(2u*32768u) => 250u*60u*5u*(0u) => 0u der Compiler sollte bei aktiver Optimierung den Ausdruck durch die Konstante 0 ersetzen können (Division duch 0 ist auch undefiniertes Verhalten und das kann da auch ignoriert werden) Und tatsächlich wird:
1 | uint32_t foo() |
2 | {
|
3 | return 256*60*TIMER_FREQ*10 / (v16*4u*2u); |
4 | }
|
zu:
1 | foo: |
2 | /* prologue: function */ |
3 | /* frame size = 0 */ |
4 | /* stack size = 0 */ |
5 | .L__stack_usage = 0 |
6 | lds r24,v16 |
7 | lds r25,v16+1 |
8 | ldi r22,lo8(0) |
9 | ldi r23,hi8(0) |
10 | ldi r24,hlo8(0) |
11 | ldi r25,hhi8(0) |
12 | /* epilogue start */ |
13 | ret |
also: return 0; Auf einer Maschine mit 32-bit ints sieht das wieder anders aus, weil bei der Muplitplikation zwischen int und uint16_t ein int rauskommt. Der Compiler könnte jetzt tatsächlich den Ausdruck kürzen und das macht der auch in der Regel. Allerdings funktioniert es bei gcc (und clang) nicht, wenn der Zähler-Ausdruck bereits einen Überlauf hat, weil der Optimierer den kompletten Ausdrucck nicht sieht und mit einem Überlauf-Wert rechnet.
Danke für die ausführlichen Infos. Dann müssen die Casts wohl bleiben... Also:
1 | v32 = 256ul x 60ul x (uint32_t)TIMER_FREQ x 10ul / (v16 x 4u x 2u); |
wobei ich hier bei 4u und 2u das "u" auch weglassen könnte. Wenn ich sicher sein will, dass die Faktoren 4 und 2 im Nenner vom Compiler mit den Konstanten im Zähler zu einer Konstante zusammgefasst werden muss ich das wohl auch genau so hinschreiben. Hatt halt irgendwie gehoft, letztenendes kann der Compiler aber nicht wissen ob mir eine kleine Ungenauigkeit bei der Konstante auch taugt, oder ob ich einen Wrap-Around haben will ohne das explizit mit Modulo-Operator hinzuschreiben. Jeder Mensch würde wohl sagen 32-Bit-Ergebnis, Wert im Zähler kleiner als 32 Bit, im Nenner weniger als 16 Bit, das soll mit 32 Bit gerechnet werden. Stephan
Stephan schrieb: > (uint32_t)TIMER_FREQ Der Typecast ist nicht nötig, da die Operanden davor ja durch den Suffix "ul" bereits bewirken, dass der Ausdruck vom Typ "unsigned long" ist. Ich hoffe mal, die "x" sollen "*" sein. ;)
Jörg Wunsch schrieb: > Der Typecast ist nicht nötig, da die Operanden davor ja durch den > Suffix "ul" bereits bewirken, dass der Ausdruck vom Typ "unsigned > long" ist. Hatte ich auch mit 99% Sicherheit so verstanden. Ich programmier MC nur sporadisch. Da kommts mir letztenendes transparenter vor das überall explizit hinzuschreiben. Aus den 256ul eine Compiler-Konstate gemacht (ul nicht mehr erkennbar), Formel umgestellt und schon gehts nicht mehr. Macht der zusätzliche Cast von der Geschwindigkeit was aus (auch wenn TIMER_FREQ eine uint16_t-Variable wäre)? Theoretisch ist ja klar, dass in (uint32_t)TIMER_FREQ nur die unteren 16 Bit interessieren können. Eine 32*16-Bit-Multiplikation wäre also ausreichend. Jörg Wunsch schrieb: > Ich hoffe mal, die "x" sollen "*" sein. ;) Ja. Das letzte mal hats mir nur " * " irgendwie verwürfelt. Ohne Leerzeichen wars mir aber zu unübersichtlich. Stephan
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.