Forum: Compiler & IDEs gcc: Seltsames Verhalten in compile-time Berechnung


von Foka M. (foka)


Lesenswert?

Hallo Zusammen,

ich will zur comple-Zeit bestimmte Konstanten berechnen. Doch aus irgend 
einem Grund, wenn ich den avr-gcc verwende, werden diese nicht so 
gerechnet wie ich es erwarten wuerde.

Die Konstanten sind jedoch richtig wenn der 'normale' gcc (ubuntu 
systemcompiler) verwendet wird.

Folgender Code sollte fuer jede test-Variable 31 ergeben (8000000/(256 * 
1000)):
1
#include <stdint.h>
2
#define LARGE_NUMBER (8000000UL)
3
4
//static const uint16_t mPrescaler = 256;
5
#define mPrescaler ((uint16_t)256)
6
7
const uint16_t myTestVariable1 = LARGE_NUMBER / (mPrescaler * 1000);
8
const uint16_t myTestVariable2 = LARGE_NUMBER / ((uint16_t)256 * 1000);
9
const uint16_t myTestVariable3 = LARGE_NUMBER / 256000;
10
11
int main() {
12
   return 0;
13
}

Uebersetzt man das mit:
1
gcc -Wall -Wextra --save-temps -c testCalc.c -o testCalc.o

Dann ist alles in Ordnung, jede Variable hat den Wert den ich erwarten 
wuerde (testCalc.s):
1
  .file  "testCalc.c"
2
  .globl  myTestVariable1
3
  .section  .rodata
4
  .align 2
5
  .type  myTestVariable1, @object
6
  .size  myTestVariable1, 2
7
myTestVariable1:
8
  .value  31
9
  .globl  myTestVariable2
10
  .align 2
11
  .type  myTestVariable2, @object
12
  .size  myTestVariable2, 2
13
myTestVariable2:
14
  .value  31
15
  .globl  myTestVariable3
16
  .align 2
17
  .type  myTestVariable3, @object
18
  .size  myTestVariable3, 2
19
myTestVariable3:
20
  .value  31
21
  .text
22
  .globl  main
23
  .type  main, @function
24
main:
25
.LFB0:
26
  .cfi_startproc
27
  pushq  %rbp
28
  .cfi_def_cfa_offset 16
29
  .cfi_offset 6, -16
30
  movq  %rsp, %rbp
31
  .cfi_def_cfa_register 6
32
  movl  $0, %eax
33
  popq  %rbp
34
  .cfi_def_cfa 7, 8
35
  ret
36
  .cfi_endproc
37
.LFE0:
38
  .size  main, .-main
39
  .ident  "GCC: (Ubuntu 5.3.1-14ubuntu2) 5.3.1 20160413"
40
  .section  .note.GNU-stack,"",@progbits

Wenn ich es jedoch mit dem avr-gcc uebersetze:
1
avr-gcc -c  -mmcu=atmega328p -Os -Wall -Wextra -Wundef --save-temps testCalc.c -o testCalc.o
Dann ist:
1
  .file  "testCalc.c"
2
__SP_H__ = 0x3e
3
__SP_L__ = 0x3d
4
__SREG__ = 0x3f
5
__tmp_reg__ = 0
6
__zero_reg__ = 1
7
  .section  .text.startup,"ax",@progbits
8
.global  main
9
  .type  main, @function
10
main:
11
/* prologue: function */
12
/* frame size = 0 */
13
/* stack size = 0 */
14
.L__stack_usage = 0
15
  ldi r24,0
16
  ldi r25,0
17
  ret
18
  .size  main, .-main
19
.global  myTestVariable3
20
  .section  .rodata
21
  .type  myTestVariable3, @object
22
  .size  myTestVariable3, 2
23
myTestVariable3:
24
  .word  31
25
.global  myTestVariable2
26
  .type  myTestVariable2, @object
27
  .size  myTestVariable2, 2
28
myTestVariable2:
29
  .word  134
30
.global  myTestVariable1
31
  .type  myTestVariable1, @object
32
  .size  myTestVariable1, 2
33
myTestVariable1:
34
  .word  134
35
  .ident  "GCC: (GNU) 6.1.0"
36
.global __do_copy_data

Ich habe auch den avr-gcc 4.9.2 ausprobiert. Ergebnis ist gleich.
Das Verhalten ist auch gleich egal ob 'mPrescaler' ein '#define' ist 
oder eine 'static const' variable (im C++ Fall).

Warum wird myTestVariable3 korrekt gerechnet, waehrend die anderen zwei 
falsch sind?
Es scheint, dass die Multiplikation im Nenner schief geht.
Mir ist auch nicht klar wie der compiler gerade auf '134' kommt...

Sitzt der Bug jetzt vor dem Rechner oder im avr-gcc?
Wenn vor dem Rechner, wo ist dann mein Denkfehler?

Vielen Dank fuer Eure Hilfe,
-Foka

von Max (Gast)


Lesenswert?

Dein uint16_t reicht nicht für 256*1000

von Max (Gast)


Lesenswert?

Mach mal 256UL * 1000.

Oder rechne in zwei Schritten: LARGE_NUMBER  256  1000

von Max (Gast)


Lesenswert?

Forum hat die Slashes verschluckt...
1
LARGE_NUMER / 256 / 1000

von Foka M. (foka)


Lesenswert?

Max schrieb:
> Forum hat die Slashes verschluckt...
>
>
1
LARGE_NUMER / 256 / 1000

Oh, prima!
Danke fuer den Tipp, so funktioniert es.

Bliebe nur noch die Frage warum der 'normale' gcc den gleichen Code 
richtig(er) uebersetzt.

-Foka

von Max (Gast)


Lesenswert?

Wahrscheinlich (ich weiß es nicht genau), weil gcc nur zu int promoted 
und "int" beim avr eben 16 Bit breit sind und beim x86 32 Bit.

von Kernelhacker (Gast)


Lesenswert?

Integer promotion ist die Ursache dieses Verhaltens.

von Markus F. (mfro)


Lesenswert?

Kernelhacker schrieb:
> Integer promotion ist die Ursache dieses Verhaltens.

Nein. Integer promotion ist zwar ein hübsches Stichwort, stimmt aber 
hier nicht. Integer promotion ist die automatische Erweiterung von 
Parametern arithmetischer Operationen, die kleiner als int sind, auf 
int.

Hier gibt's keine integer promotion, weil die Konstanten schon int sind.

Das ist ein ganz simpler Überlauf bei der Berechnung des 
Klammerausdrucks.

256 * 1000 ist nun mal 256000 und das passt nicht in ein AVR 16-bit int.

von Carl D. (jcw2)


Lesenswert?

Vermutlich hat der avr-gcc über Warnungen auch gemeldet, daß es da einen 
int-Überlauf gibt, nur - wer liest schon Warnungen. Ist doch einfacher 
einen Compiler-Fehler zu vermuten.  ;-(

Der Compiler muß im Übrigen zur Compile-Time exakt so rechnen, wie es 
die HW auch tun würde, inkl. Überlauf, sonst wäre das tatsächlich ein 
Compiler-Fehler.

von Foka M. (foka)


Lesenswert?

Carl D. schrieb:
> Vermutlich hat der avr-gcc über Warnungen auch gemeldet, daß es da einen
> int-Überlauf gibt, nur - wer liest schon Warnungen. Ist doch einfacher
> einen Compiler-Fehler zu vermuten.  ;-(
>

Das stimmt so nicht.
Wenn Du den Code, den ich oben gepostet habe, mit dem avr-gcc 
duchrkompiliertst wirst Du feststellen, dass der Compiler nicht warnt.
Ich gehoere zu der Sorte die immer das '-Wall -Wextra' mit auf die 
command-line aufnehmen.

Aufgefallen ist es mir auch nur weil ich eine Art 'sanity check' 
eigebaut habe:
1
typedef struct { uint8_t a[255 - (3 * myTestVariable1)]; } Error_TestVariableLargerThan255;

-Foka

von Bernd K. (prof7bit)


Lesenswert?

Markus F. schrieb:
> Kernelhacker schrieb:
>> Integer promotion ist die Ursache dieses Verhaltens.
>
> Nein. Integer promotion ist zwar ein hübsches Stichwort, stimmt aber
> hier nicht.

Doch. Integer-promotion ist der Grund warum es auf dem x86 nicht 
überäuft.

von Foka M. (foka)


Lesenswert?

Ich habe mir noch kurz angeschaut was der 'normale' gcc im 32-Bit modus 
macht wenn im Nenner der Ueberlauf passiert:
1
#include <stdint.h>
2
#define LARGE_NUMBER (24500000000)
3
#define mPrescaler (70000)
4
5
volatile const uint32_t myTestVariable1 = LARGE_NUMBER / (mPrescaler *  70000);
6
volatile const uint32_t myTestVariable2 = LARGE_NUMBER / (70000 * 70000);
7
volatile const uint32_t myTestVariable3 = LARGE_NUMBER / 4900000000;
8
9
int main() {
10
   return 0;
11
}

compiliert mit:
1
gcc -m32 -Wall -Wextra --save-temps -c testCalc.c -o testCalc.o

ergibt auch eine Warnung:
1
testCalc.c:19:68: warning: integer overflow in expression [-Woverflow]
2
 volatile const uint32_t myTestVariable1 = LARGE_NUMBER / (mPrescaler *  70000);
3
                                                                    ^
4
testCalc.c:20:66: warning: integer overflow in expression [-Woverflow]
5
 volatile const uint32_t myTestVariable2 = LARGE_NUMBER / (70000 * 70000);

Und die, vom compiler angemeckerten, Variablen sind auch falsch 
berechnet.
So wuerde ich es vom avr-gcc erwarten.
Der Vorschlag von Max funktioniert hier ueberigens auch sehr gut.

Allerdings gilt es auch nicht mehr wenn ich den Code mit dem g++ 
uebersetze und wie folgt anpasse:
1
#include <stdint.h>
2
#define LARGE_NUMBER (24500000000)
3
static const uint32_t mPrescaler = 70000;
4
5
volatile const uint32_t myTestVariable1 = LARGE_NUMBER / (mPrescaler *  70000);
6
volatile const uint32_t myTestVariable2 = LARGE_NUMBER / (70000 * 70000);
7
volatile const uint32_t myTestVariable3 = LARGE_NUMBER / 4900000000;
8
9
int main() {
10
   return 0;
11
}

Das ergibt nur eine Warning ufer 'myTestVariable2'. 'myTestVariable1' 
ist falsch berechnet und es gab keine Warnung:
1
g++ -m32 -Wall -Wextra --save-temps -c testCalc.c -o testCalc.o
2
testCalc.c:20:66: warning: integer overflow in expression [-Woverflow]
3
 volatile const uint32_t myTestVariable2 = LARGE_NUMBER / (70000 * 70000);
4
                                                                  ^
5
mi@thinkfoka:~/devel/RTV_RI$ cat testCalc.s 
6
  .file  "testCalc.c"
7
  .section  .rodata
8
  .align 4
9
  .type  _ZL10mPrescaler, @object
10
  .size  _ZL10mPrescaler, 4
11
_ZL10mPrescaler:
12
  .long  70000
13
  .data
14
  .align 4
15
  .type  _ZL15myTestVariable1, @object
16
  .size  _ZL15myTestVariable1, 4
17
_ZL15myTestVariable1:
18
  .long  40
19
  .align 4
20
  .type  _ZL15myTestVariable2, @object
21
  .size  _ZL15myTestVariable2, 4
22
_ZL15myTestVariable2:
23
  .long  40
24
  .align 4
25
  .type  _ZL15myTestVariable3, @object
26
  .size  _ZL15myTestVariable3, 4
27
_ZL15myTestVariable3:
28
  .long  5
29
  .text
30
  .globl  main
31
  .type  main, @function
32
main:
33
.LFB0:
34
  .cfi_startproc
35
  pushl  %ebp
36
  .cfi_def_cfa_offset 8
37
  .cfi_offset 5, -8
38
  movl  %esp, %ebp
39
  .cfi_def_cfa_register 5
40
  movl  $0, %eax
41
  popl  %ebp
42
  .cfi_restore 5
43
  .cfi_def_cfa 4, 4
44
  ret
45
  .cfi_endproc
46
.LFE0:
47
  .size  main, .-main
48
  .ident  "GCC: (Ubuntu 5.3.1-14ubuntu2) 5.3.1 20160413"
49
  .section  .note.GNU-stack,"",@progbits

Auch wenn das Verhalten schon etwas besser ist als das von avr-gcc ist 
es leider immer noch nicht perfekt und man muss doch hoellisch 
aufpassen.


-Foka

von Bernd K. (prof7bit)


Lesenswert?

Foka M. schrieb:
> und man muss doch hoellisch aufpassen.

Ja.

Aber mit der Zeit bekommt man ein Auge dafür.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Der GCC gibt bei Unsigned-Operationen niemals eine Overflow-Warnung aus,
da Unsigned-Operationen nach C-Standard nicht überlaufen können, sondern
per modulo 2**n auf den gültigen Wertebereich reduziert werden.

Deswegen warnt der AVR-GCC zwar bei

1
  256 * 1000

nicht aber (wie im Eröffnungsbeitrag) bei

1
  (uint16_t)256 * 1000

von Kernelhacker (Gast)


Lesenswert?

Markus F. schrieb:
> Kernelhacker schrieb:
>> Integer promotion ist die Ursache dieses Verhaltens.
>
> Nein. Integer promotion ist zwar ein hübsches Stichwort, stimmt aber
> hier nicht. Integer promotion ist die automatische Erweiterung von
> Parametern arithmetischer Operationen, die kleiner als int sind, auf
> int.

Doch natürlich handelt es sich bei der Ursache des unterschiedlichen 
Verhaltens hier um Promotion.
Der Code ist:

(uint16_t)256 * 1000

Das funktioniert nicht, wenn es keine Promotion von uint16_t auf ein 
32 Bit int gibt. Wie es eben auf dem AVR der Fall ist, weil int dort 16 
Bit hat.

Das es ohne den Cast genau so wenig funktioniert, ist ja wieder eine 
separate Sache.

von Markus F. (mfro)


Lesenswert?

Bernd K. schrieb:
> Markus F. schrieb:
>> Kernelhacker schrieb:
>>> Integer promotion ist die Ursache dieses Verhaltens.
>>
>> Nein. Integer promotion ist zwar ein hübsches Stichwort, stimmt aber
>> hier nicht.
>
> Doch. Integer-promotion ist der Grund warum es auf dem x86 nicht
> überäuft.

Blödsinn. Der Grund, warum es auf dem x86 nicht überläuft ist die Größe 
eines ints auf dieser Plattform.

Wie willst Du ein int auf ein int promoten?

von Kernelhacker (Gast)


Lesenswert?

Markus F. schrieb:
> Wie willst Du ein int auf ein int promoten?

Das ist nicht der Fall. Es ist eine uint16_t zu int Promotion auf x86.

von Markus F. (mfro)


Lesenswert?

Kernelhacker schrieb:
> Markus F. schrieb:
>> Wie willst Du ein int auf ein int promoten?
>
> Das ist nicht der Fall. Es ist eine uint16_t zu int Promotion auf x86.

Stimmt. Ich hab' den Cast nicht gesehen, sorry.

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
Noch kein Account? Hier anmelden.