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.