Forum: Compiler & IDEs Hilfe - AVR-GCC "optimiert" Schleife zur Endlosschleife


von Holger G. (lutex)


Lesenswert?

Hallo allerseits,

mein Vertrauen in den AVR-GCC stellt dieser gerade auf eine sehr harte 
Probe: Bei folgendem C-Code
1
#include <avr/wdt.h>
2
3
int main (void)
4
{
5
    int i = 0;
6
    while (--i)
7
        wdt_reset();
8
    
9
    for (;;);
10
}

macht er mir aus der while-Verzögerungsschleife diesen Assemblercode 
(Auszug aus der LSS-Datei):
1
00000024 <main>:
2
3
int main (void)
4
{
5
    int i = 0;
6
    while (--i)
7
        wdt_reset();
8
  24:   a8 95       wdr
9
  26:   fe cf       rjmp  .-4    ; 0x24 <main>
10
11
00000028 <_exit>:
12
  28:   f8 94       cli

Das ist eine Endlosschleife zwischen Adresse 24 und 26 und hat nichts 
mit dem C-Quelltext zu tun!

Das Verrückte ist: Wenn ich aus dem "int" ein "unsigned int" mache, dann 
funktioniert alles:
1
#include <avr/wdt.h>
2
3
int main (void)
4
{
5
    unsigned int i = 0;
6
    while (--i)
7
        wdt_reset();
8
    
9
    for (;;);
10
}

LSS-Datei:
1
00000024 <main>:
2
#include <avr/wdt.h>
3
4
int main (void)
5
{
6
  24:   80 e0       ldi  r24, 0x00      ; 0
7
  26:   90 e0       ldi  r25, 0x00      ; 0
8
  28:   01 c0       rjmp  .+2           ; 0x2c <main+0x8>
9
    unsigned int i = 0;
10
    while (--i)
11
        wdt_reset();
12
  2a:   a8 95       wdr
13
#include <avr/wdt.h>
14
15
int main (void)
16
{
17
    unsigned int i = 0;
18
    while (--i)
19
  2c:   01 97       sbiw  r24, 0x01    ; 1
20
  2e:   e9 f7       brne  .-6          ; 0x2a <main+0x6>
21
  30:   ff cf       rjmp  .-2          ; 0x30 <main+0xc>
22
23
00000032 <_exit>:
24
  32:   f8 94       cli


Kompiliert wurde unter WinAVR 20100110 mit dem gcc 4.3.3 folgendermaßen 
(von Makefile gesteuert):
1
avr-gcc  -mmcu=at90s2313 -Wall -gdwarf-2 -std=gnu99 -DF_CPU=8000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -MD -MP -MT main.o -MF dep/main.o.d  -c  main.c
2
avr-gcc -mmcu=at90s2313  main.o     -o test.elf
3
avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature  test.elf test.hex
4
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 --no-change-warnings -O ihex test.elf test.eep || exit 0
5
avr-objdump -h -S test.elf > test.lss
6
7
AVR Memory Usage
8
----------------
9
Device: at90s2313
10
11
Program:      54 bytes (2.6% Full)
12
(.text + .data + .bootloader)
13
14
Data:          0 bytes (0.0% Full)
15
(.data + .bss + .noinit)

Was passiert hier? Warum "optimiert" der GCC die Schleife mit der 
"int"-Variablen zur Endlosschleife? Beim Kompilieren kommen keine 
Warnungen.

Viele Grüße, Holger

von (prx) A. K. (prx)


Lesenswert?

Holger G. schrieb:
>     int i = 0;
>     while (--i)
>         wdt_reset();

Recht hat er. Du vertraust hier auf den (negativen) Überlauf einer 
Variablen mit Vorzeichen. Das ist undefiniertes Verhalten.

von Walter Tarpan (Gast)


Lesenswert?

Holger G. schrieb:
> Das ist eine Endlosschleife zwischen Adresse 24 und 26 und hat nichts
> mit dem C-Quelltext zu tun!

Eigentlich schon:

> for (;;);
> }
>

Wo ist das Problem?

von (prx) A. K. (prx)


Lesenswert?

Holger G. schrieb:
> Das Verrückte ist: Wenn ich aus dem "int" ein "unsigned int" mache, dann
> funktioniert alles:

Bei vorzeichenlosen Variablen ist das Überlaufverhalten klar definiert.

Walter Tarpan schrieb:
> Wo ist das Problem?

Sein Problem liegt in der Schleife davor.

von Faultier (Gast)


Lesenswert?

Ganz offen gesagt, lese ich sowas immer in Erwartung der Stelle, an der 
auf die Seite im K&R verwiesen wird, an der steht, daß der inkriminierte 
Code mit definiertes Verhalten beschrieben ist.

Das kommt allerdings wesentlich seltener vor, als erschüttertes 
Vertrauen oder die Unterstellung eines Compilerfehlers.

Irgendwie auffällig.

von Holger G. (lutex)


Lesenswert?

A. K. schrieb:

> Recht hat er. Du vertraust hier auf den (negativen) Überlauf einer
> Variablen mit Vorzeichen. Das ist undefiniertes Verhalten.

Versteh' ich nicht. Was soll denn daran undefinierter sein als der 
Rollover einer vorzeichenlosen 16-bit-Variablen beim "Dekrementieren" 
von 0 auf 65535?

Abgesehen davon darf er von mir aus warnen, schimpfen, Exceptions werfen 
- nur nicht stillschweigend so einen Blödsinn compilieren.

von c.m. (Gast)


Lesenswert?

ich hätte jetzt darauf getippt das die decrementierung einer variablen 
immer "true" zurückgibt - aber ich bin c-anfänger.
kommt irgendwie aus der gleichen ecke wie der beliebte java-fehler
'while (var = 5) {...}'.

von (prx) A. K. (prx)


Lesenswert?

Holger G. schrieb:
> Versteh' ich nicht. Was soll denn daran undefinierter sein als der
> Rollover einer vorzeichenlosen 16-bit-Variablen beim "Dekrementieren"
> von 0 auf 65535?

Diesen Unterschied kannst du auch unter "par ordre du mufti" 
zusammenfassen. Das steht schlicht so im C-Standard.

> Abgesehen davon darf er von mir aus warnen, schimpfen, Exceptions werfen
> - nur nicht stillschweigend so einen Blödsinn compilieren.

Der Compiler darf alles optimieren, was im Rahmen der Sprachdefinition 
zum richtigen Ergebnis führt. Und das ist hier der Fall.

Höflicher wärs natürlich, wenn er nett darauf hinweist, dass er hier 
eine Schluderei des Programmierers ausnutzt. Wobei er nicht immer 
erkennt, dass dies so ist.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Ich würde sagen: <0 ist beim gcc FALSE. Also hat er die komplette 
Schleife rausgeworfen, und die for() blieb übrig.

von (prx) A. K. (prx)


Lesenswert?

Random ... schrieb:
> Ich würde sagen: <0 ist beim gcc FALSE.

Bei "int"?

Holger G. schrieb:
> Abgesehen davon darf er von mir aus warnen, schimpfen, Exceptions werfen
> - nur nicht stillschweigend so einen Blödsinn compilieren.

Der Compiler erkennt, dass die Bedingung innerhalb der Sprachdefinition 
nie falsch sein wird. Und er erkennt auch, dass "i" nirgends sonst 
verwendet wird. Also ist es äquivalent zu
  while (1) wdt_reset();

von Random .. (thorstendb) Benutzerseite


Lesenswert?

c.m. schrieb:
> ich hätte jetzt darauf getippt das die decrementierung einer variablen
> immer "true" zurückgibt - aber ich bin c-anfänger.
> kommt irgendwie aus der gleichen ecke wie der beliebte java-fehler
> 'while (var = 5) {...}'.

Das Decrementieren einer Variablen gibt natürlich das Ergebnis nach dem 
Decrement wieder **kopfschüttel**

von Matthias (Gast)


Lesenswert?

1
  int i = 0;
2
    while (--i)
3
        wdt_reset();

Wer so einen Sch... schreibt muss sich auch nicht wundern wenn ihm die 
selbige um die Ohren fliegt.

Vermutlich war das gemeint:
1
 unsigned int i = UINT_MAX;
2
 while (i--) {
3
  wdt_reset();
4
 }

Was hat man davon nicht genau das zu schreiben was man auch meint? Der 
Compiler ist offenbar nicht weiblich.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

A. K. schrieb:
> Random ... schrieb:
>> Ich würde sagen: <0 ist beim gcc FALSE.
>
> Bei "int"?
>
> Holger G. schrieb:
>> Abgesehen davon darf er von mir aus warnen, schimpfen, Exceptions werfen
>> - nur nicht stillschweigend so einen Blödsinn compilieren.
>
> Der Compiler erkennt, dass die Bedingung innerhalb der Sprachdefinition
> nie falsch sein wird. Und er erkennt auch, dass "i" nirgends sonst
> verwendet wird. Also ist es äquivalent zu
>   while (1) wdt_reset();

Da hast du dich verlesen :-)
Der Compiler schmeisst die ganze while() Schleife raus und lässt nur die 
for() dahinter drin.
Das deckt sich mit meiner Annahme, dass werte <0 FALSE entsprechen. Und 
von daher kann er while(FALSE) rausnehmen.

> Der Compiler erkennt, dass die Bedingung innerhalb der Sprachdefinition
> nie falsch sein wird.
a) Nehmen wir an, der zählt tatsächlich los, dann ist mind. (--i == 0) 
FALSE :-)
b) dann würde nach deiner Annahme da ein while(TRUE) WDT(); stehen 
bleiben.

: Bearbeitet durch User
von Peter II (Gast)


Lesenswert?

Random ... schrieb:
> Da hast du dich verlesen :-)
> Der Compiler schmeisst die ganze while() Schleife raus und lässt nur die
> for() dahinter drin.

und warum wird denn der Wachhund resettet?

> 24:   a8 95       wdr

von (prx) A. K. (prx)


Lesenswert?

Random ... schrieb:
> Der Compiler schmeisst die ganze while() Schleife raus und lässt nur die
> for() dahinter drin.

Nein, denn es bleibt das übrig:
  24:   a8 95       wdr
  26:   fe cf       rjmp  .-4    ; 0x24 <main>
und das ist eben
  while (1) wdt_reset();

Sorry für das Ungemach, aber ich kann lesen. Insbesondere den Output 
von Compilern. ;-)

von (prx) A. K. (prx)


Lesenswert?

Random ... schrieb:
> a) Nehmen wir an, der zählt tatsächlich los, dann ist mind. (--i == 0)
> FALSE :-)

Aber das steht da nicht. Da steht
  while (--i)
     wdt_reset();
also
  while (--i != 0)
     wdt_reset();
und --i wird bei int und Anfangswert 0 niemals wieder definiert 0.

Was ist los? Schlechten Tag erwischt?

: Bearbeitet durch User
von Holger G. (lutex)


Lesenswert?

A. K. schrieb:

> Der Compiler darf alles optimieren, was im Rahmen der Sprachdefinition
> zum richtigen Ergebnis führt. Und das ist hier der Fall.

Wäre es nicht sinnvoller, wenn der Compiler alles optimieren darf, was 
zum selben Verhalten wie der unoptimierte Code führt? Alles andere ist 
doch kreuzgefährlich und nützt absolut niemandem.

> Höflicher wärs natürlich, wenn er nett darauf hinweist, dass er hier
> eine Schluderei des Programmierers ausnutzt. Wobei er nicht immer
> erkennt, dass dies so ist.

Mit Höflichkeit hat das nicht viel zu tun. Ein kommerzielles Produkt 
würde Spießruten laufen, wenn es den Programmierer unter lapidarem 
Verweis auf den C-Standard so im Regen stehen lässt. Aber Open Source 
darf das wohl.

von Oliver (Gast)


Lesenswert?

Vermutlich erkennt der Compiler, daß das ganze Programm mit 
zigtausendfachem wdt-reset() sowieso völliger Blödsinn ist, und macht 
daraus was sinnvolles ;)

Oliver

von Peter D. (peda)


Lesenswert?

Den GCC-lern sitzt machnmal der Schalk im Nacken und dann nehmen sie die 
Variante, die sich der Autor möglichst wenig vorgestellt hat.

Da signed Unterlauf nicht definiert ist, könnte es ja sein, daß der Wert 
beim Minimalwert kleben bleibt.

Wird mit ner positiven Zahl initialisiert, funktioniert es.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

A. K. schrieb:
> Random ... schrieb:
>> Der Compiler schmeisst die ganze while() Schleife raus und lässt nur die
>> for() dahinter drin.
>
> Nein, denn es bleibt das übrig:
>   24:   a8 95       wdr
>   26:   fe cf       rjmp  .-4    ; 0x24 <main>
> und das ist eben
>   while (1) wdt_reset();
>
> Sorry für das Ungemach, aber ich kann lesen. Insbesondere den Output
> von Compilern. ;-)

Tatsächlich sehe ich hier nur das ergebnis der for() schleife :-)
Wo ist denn der call zum wdt?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Random ... schrieb:

> Der Compiler schmeisst die ganze while() Schleife raus und lässt nur die
> for() dahinter drin.

Nein.

Hier nochmal im Klartext, also generierter Assemblercode (in diesem
Falle vom GCC 4.7.2):
1
.L2:
2
/* #APP */
3
 ;  7 "foo.c" 1
4
        wdr
5
 ;  0 "" 2
6
/* #NOAPP */
7
        rjmp .L2

Das ist nicht nur irgendwie ein Ausschnitt, nein, das ist das
komplette main(), welches er generiert.

> Das deckt sich mit meiner Annahme, dass werte <0 FALSE entsprechen. Und
> von daher kann er while(FALSE) rausnehmen.

Deine Annahme ist falsch.  Der C-Standard definiert ganz eindeutig,
dass nur der Wert 0 als Wahrheitswert “false” entspricht, alle anderen
Werte entsprechen “true”.

Es ist schon so, wie A. K. es schreibt.  Der Code verlässt sich darauf,
dass der Überlauf von -MAX_INT - 1 beim weiteren Herunterzählen
eine 0 ergibt.  Ein solcher Überlauf ist aber in C nicht definiert.

Wenn man -Wstrict-overflow mit in die Optionen aufnimmt, bekommt man
auch die entsprechende Warnung:
1
foo.c: In function 'main':
2
foo.c:6:11: warning: assuming signed overflow does not occur when simplifying conditional to constant [-Wstrict-overflow]

Alternativ bekommt man mit -fno-strict-overflow das vom TE erhoffte
Ergebnis:
1
        ldi r24,0
2
        ldi r25,0
3
        rjmp .L2
4
.L3:
5
/* #APP */
6
 ;  7 "foo.c" 1
7
        wdr
8
 ;  0 "" 2
9
/* #NOAPP */
10
.L2:
11
        sbiw r24,1
12
        brne .L3
13
.L4:
14
        rjmp .L4

von Faultier (Gast)


Lesenswert?

>Wo ist denn der call zum wdt?

Da:

>   24:   a8 95       wdr

von (prx) A. K. (prx)


Lesenswert?

Holger G. schrieb:
> Wäre es nicht sinnvoller, wenn der Compiler alles optimieren darf, was
> zum selben Verhalten wie der unoptimierte Code führt?

Ist dir eigentlich klar, was das für Folgen hätte?

Probiers mal im Kleinen aus: Schreib in deinem Program vor alle 
Variablen "volatile" davor, und schau an was dabei rauskommt. Ist ja ein 
Standardproblem, wenn der Compiler Zugriffe wegoptimiert und damit die 
Änderung im Interrupt übersieht.

von Matthias (Gast)


Lesenswert?

Nochmal um klar zu stellen warum der Compiler das komplette 
while-Statement entsorgt. Dort steht für ihn nachweisbar in diesem 
Scope:
1
while (-1){
2
  wdt_reset();
3
}

Das heisst die while kann nicht ein einziges Mal true werden und ist 
somit sinnlos. Das gleiche gilt damit für i, welches gleich mitentsorgt 
wird.

Am Ende bleibt das:
1
for(;;);

von Peter II (Gast)


Lesenswert?

Random ... schrieb:
> Tatsächlich sehe ich hier nur das ergebnis der for() schleife :-)
> Wo ist denn der call zum wdt?

es gibt kein call, das erledigt ein asm befehlt.
>   24:   a8 95       wdr

von Peter II (Gast)


Lesenswert?

Matthias schrieb:
> Nochmal um klar zu stellen warum der Compiler das komplette
> while-Statement entsorgt. Dort steht für ihn nachweisbar in diesem
> Scope:

mach er doch überhaupt nicht. Es wird nur eine endlosschleife.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

A. K. schrieb:
> Random ... schrieb:
>> a) Nehmen wir an, der zählt tatsächlich los, dann ist mind. (--i == 0)
>> FALSE :-)
>
> Aber das steht da nicht. Da steht
>   while (--i)
>      wdt_reset();
> also
>   while (--i != 0)
>      wdt_reset();
> und --i wird bei int und Anfangswert 0 niemals wieder definiert 0.
>
> Was ist los? Schlechten Tag erwischt?

Also, wenn <0 FALSE entspricht wird der while - Ausdruck genau einmal 
mit while(-1) == while(FALSE) aufgerufen. Damit kann der Compiler den 
ganzen Aufruf incl. wdt rauswerfen.

Ich meinte, falls <0 TRUE entspräche und der einmal in die Runde 
laufen würde, wäre irgendwann das Ergebnis von --i NULL.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Peter II schrieb:
> Matthias schrieb:
>> Nochmal um klar zu stellen warum der Compiler das komplette
>> while-Statement entsorgt. Dort steht für ihn nachweisbar in diesem
>> Scope:
>
> mach er doch überhaupt nicht. Es wird nur eine endlosschleife.

Nope, der macht keine Endlosschleife. Die Endlosschleife ist das for() !

von Faultier (Gast)


Lesenswert?

>Holger G. schrieb:
> Wäre es nicht sinnvoller, wenn der Compiler alles optimieren darf, was
> zum selben Verhalten wie der unoptimierte Code führt?

Genau das tut er ja.
Es ist ja nur eine von mehreren Möglichkeit, das im Standard 
"undefiniertes" Verhalten, da es ohne Optimierung ja doch in irgendeiner 
Weise entschieden werden muss, genau zu dem Verhalten führt, das Du für 
sinnvoll hälst.
Genauso plausibel wäre etwa ein "stehenbleiben" bei INT_MIN oder auch 
anderes.

von Peter II (Gast)


Lesenswert?

Random ... schrieb:
> Nope, der macht keine Endlosschleife. Die Endlosschleife ist das for() !

und warum wird dann der Watchdog resettet?

>   24:   a8 95       wdr
>   26:   fe cf       rjmp  .-4    ; 0x24 <main>

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Sorry den Befehl wdr kannte ich nicht (bin nicht mit AVR asm unterwegs). 
Jetzt wirds interessant :-)

von Peter II (Gast)


Lesenswert?

der Compiler sagt es ein wert der kleiner 0 ist und von dem etwas 
abgezogen wird, kann niemals wieder 0 werden. -> Endlosschleife.

von Faultier (Gast)


Lesenswert?

>Also, wenn <0 FALSE entspricht wird der while - Ausdruck genau einmal
>mit while(-1) == while(FALSE) aufgerufen. Damit kann der Compiler den
>ganzen Aufruf incl. wdt rauswerfen.

An dem Argument ist etwas dran, denke ich. Der Punkt ist nur, das eben 
die GCC-Autoren sich anders entschieden haben, und dabei die Tatsache, 
dass das Verhalten für solche Fälle "implementierungsabhängig" ist zu 
ihrer Rechtferigung benutzen können. Durchaus legitim, meine ich.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Faultier schrieb:
>>Also, wenn <0 FALSE entspricht wird der while - Ausdruck genau einmal
>>mit while(-1) == while(FALSE) aufgerufen. Damit kann der Compiler den
>>ganzen Aufruf incl. wdt rauswerfen.
>
> An dem Argument ist etwas dran, denke ich. Der Punkt ist nur, das eben
> die GCC-Autoren sich anders entschieden haben, und dabei die Tatsache,
> dass das Verhalten für solche Fälle "implementierungsabhängig" ist zu
> ihrer Rechtferigung benutzen können. Durchaus legitim, meine ich.

Wenn !=0 TRUE entspricht (also auch <0) sollte - rein technisch - 
irgendwann der Überlauf und dann auch die (--i == NULL) kommen. (ich 
schreibe --i, da ja das Ergebnis des Aufrufes, und nicht der aktuelle 
Wert von i zählt).

Aber da schlägt wohl das Argument von Peter II (Gast) zu.

von Holger G. (lutex)


Lesenswert?

A. K. schrieb:
> Holger G. schrieb:
>> Wäre es nicht sinnvoller, wenn der Compiler alles optimieren darf, was
>> zum selben Verhalten wie der unoptimierte Code führt?
>
> Ist dir eigentlich klar, was das für Folgen hätte?
>
> Probiers mal im Kleinen aus: Schreib in deinem Program vor /alle/
> Variablen "volatile" davor, und schau an was dabei rauskommt. Ist ja ein
> Standardproblem, wenn der Compiler Zugriffe wegoptimiert und damit die
> Änderung im Interrupt übersieht.

"volatile" bezieht sich auf die Vermeidung von Seiteneffekten durch 
Interrupts. Das ist aber etwas anderes, als einen deterministischen 
Programmablauf umzuwürfeln. Ich hab grad noch mal ohne Optimierungen 
kompiliert und folgenes erhalten:
1
  int i = 0;
2
  2e:  1a 82         std  Y+2, r1  ; 0x02
3
  30:  19 82         std  Y+1, r1  ; 0x01
4
  32:  01 c0         rjmp  .+2        ; 0x36 <__CCP__+0x2>
5
    while (--i)
6
    wdt_reset();
7
  34:  a8 95         wdr
8
#include <avr/wdt.h>
9
10
int main (void)
11
{
12
  int i = 0;
13
    while (--i)
14
  36:  89 81         ldd  r24, Y+1  ; 0x01
15
  38:  9a 81         ldd  r25, Y+2  ; 0x02
16
  3a:  01 97         sbiw  r24, 0x01  ; 1
17
  3c:  9a 83         std  Y+2, r25  ; 0x02
18
  3e:  89 83         std  Y+1, r24  ; 0x01
19
  40:  89 81         ldd  r24, Y+1  ; 0x01
20
  42:  9a 81         ldd  r25, Y+2  ; 0x02
21
  44:  00 97         sbiw  r24, 0x00  ; 0
22
  46:  b1 f7         brne  .-20       ; 0x34 <__CCP__>
23
  48:  ff cf         rjmp  .-2        ; 0x48 <__SREG__+0x9>
24
25
0000004a <_exit>:
26
  4a:  f8 94         cli

Hier macht er es also so, wie es dasteht, und pfeift auf den C-Standard. 
Und ich hätte mindestens erwartet, daß er mir eine Warnung ausgibt, wenn 
er diese eindeutig endende Schleife entfernt und einfach durch eine 
Endlosschleife ersetzt.

von (prx) A. K. (prx)


Lesenswert?

Random ... schrieb:
> Wenn !=0 TRUE entspricht (also auch <0) sollte - rein technisch -
> irgendwann der Überlauf und dann auch die (--i == NULL) kommen.

Technisch geht auch mehr. Beispielsweise saturierende Arithmetik, dann 
bleibt der Wert bei MIN_INT kleben. Wäre bei DSPs denkbar. Es könnte 
auch einen Overflow-Trap geben.

von Holger G. (lutex)


Lesenswert?

Faultier schrieb:
>>Holger G. schrieb:
>> Wäre es nicht sinnvoller, wenn der Compiler alles optimieren darf, was
>> zum selben Verhalten wie der unoptimierte Code führt?
>
> Genau das tut er ja.
> Es ist ja nur eine von mehreren Möglichkeit, das im Standard
> "undefiniertes" Verhalten, da es ohne Optimierung ja doch in irgendeiner
> Weise entschieden werden muss, genau zu dem Verhalten führt, das Du für
> sinnvoll hälst.
> Genauso plausibel wäre etwa ein "stehenbleiben" bei INT_MIN oder auch
> anderes.

Wenn er also ein Konstrukt erkennt, das wie meines laut Standard zu 
"undefiniertem" Verhalten führt und er einen Ausweg finden muss - ist es 
da zuviel verlangt, mich davon per Warnung in Kenntnis zu setzen? Genau 
das ist ja der Punkt, daß ich nicht weiß, wieviele solche Eier noch im 
eigenen (oder in fremdem) Code versteckt sind, und der einzige, der mich 
darüber informieren könnte, eben der gcc, es nicht tut!

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Faultier schrieb:
> Der Punkt ist nur, das eben die GCC-Autoren sich anders entschieden
> haben, und dabei die Tatsache, dass das Verhalten für solche Fälle
> "implementierungsabhängig" ist zu ihrer Rechtferigung benutzen können.

Nein, es ist nicht "implementierungsabhängig", sondern es ist
undefiniert.  Der Standard schreibt dazu einleitend:
1
  3.4.3
2
1 undefined behavior
3
  behavior, upon use of a nonportable or erroneous program construct or of erroneous data,
4
  for which this International Standard imposes no requirements
5
2 NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable
6
  results, to behaving during translation or program execution in a documented manner characteristic of the
7
  environment (with or without the issuance of a diagnostic message), to terminating a translation or
8
  execution (with the issuance of a diagnostic message).
9
3 EXAMPLE         An example of undefined behavior is the behavior on integer overflow.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Holger G. schrieb:

> Wenn er also ein Konstrukt erkennt, das wie meines laut Standard zu
> "undefiniertem" Verhalten führt und er einen Ausweg finden muss - ist es
> da zuviel verlangt, mich davon per Warnung in Kenntnis zu setzen?

Du musst die Warnung einschalten (wie alle anderen Warnungen auch).
Wie das geht, schrieb ich weiter oben.

Das Einzige, was man den GCC-Entwicklern hier vorwerfen könnte ist,
dass sie diese Warnung nicht bei -Wextra automatisch mit aktivieren.

p.s.:

Holger G. schrieb:
> Und ich hätte mindestens erwartet, daß er mir eine Warnung ausgibt, wenn
> er diese eindeutig endende Schleife entfernt und einfach durch eine
> Endlosschleife ersetzt.

Dass er deine "eindeutig endende" Schleife entfernt, ist ja dann nur
eine Folge davon.  An der Stelle, wo er das tut, weiß er vermutlich
gar nicht mehr, was die Ursache dafür war.

: Bearbeitet durch Moderator
von Faultier (Gast)


Lesenswert?

@  Random

> ... - rein technisch - ...

Das ist kein festumrissener Begriff, mit dessen Hilfe man in solchen 
Diskussionen argumentieren kann.

Dein Argument setzt voraus, das die Konvention, das Integers in 
begrenzten physischen Speichern repräsentiert wird, hier als allgemein 
gültig und relevant angesehen wird. Aber: Das ist eben eine Konvention. 
Es ergibt sich nicht zwingend.
Es ist nämlich auch plausibel hier "rein mathematisch" (und dieser 
Begriff ist wesentlich besser definiert) zu argumentieren, dass eine 
vorzeichenbehaftete Zahl, falls sie bgeginnend bei Null dekrementiert 
wird, IMMER negativ sein wird. Daher also auch der Vergleich ((--i) != 
0) auch IMMER falsch sein wird.

Also: Beide Standpunkte sind plausibel.

von Faultier (Gast)


Lesenswert?

@ Jörg Wunsch

Ja. Richtig. "Undefiniert". Eine Unaufmerksamkeit bei der Formulierung 
von mir. Ich meinte auch undefiniert.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Faultier schrieb:
> Eine Unaufmerksamkeit bei der Formulierung von mir. Ich meinte auch
> undefiniert.

Die ist aber durchaus folgenschwer.

"Implementierungsabhängig" (im Sinne von "implementation-defined")
ist nämlich ein sehr wohl definiertes Verhalten, bei dem sich ein
Compiler eine bestimmte Variante aus mehreren aussuchen kann, diese
aber dann dokumentieren muss.  Unter gleichen Umständen muss er sich
dann auch jedesmal gleich entscheiden.

Bei undefiniertem Verhalten muss der Compiler rein gar nichts, er
muss es nichtmal bemerken.  Er darf es auch bei jedem 13. Compilevorgang
völlig anders umsetzen als sonst.

: Bearbeitet durch Moderator
von (prx) A. K. (prx)


Lesenswert?

Holger G. schrieb:
> Wenn er also ein Konstrukt erkennt, das wie meines laut Standard zu
> "undefiniertem" Verhalten führt und er einen Ausweg finden muss

Falsch gedacht. Es ist der Programmierer, der einen Ausweg finden muss. 
Der Compiler muss es nicht, der hat nämlich kein Problem damit.

In diesem Fall freilich entdeckt er, dass der Programmierer Mist gebaut 
hat. Weshalb er davor nicht stets warnt, sondern nur auf Anforderung, 
weiss ich nicht. Möglicherweise gibt es zu viel funktionierenden Code, 
der haufenweise solche Warnungen wirft. Aber mindestens in -Wextra hätte 
das wohl schon hineingepasst.

Aber es kann auch vorkommen, dass Optimierungen aufgrund undefinierten 
Verhaltens durch den Compiler nicht klar ersichtlich sind.

von Holger G. (lutex)


Lesenswert?

Jörg Wunsch schrieb:
> Du musst die Warnung einschalten (wie alle anderen Warnungen auch).
> Wie das geht, schrieb ich weiter oben.
>
> Das Einzige, was man den GCC-Entwicklern hier vorwerfen könnte ist,
> dass sie diese Warnung nicht bei -Wextra automatisch mit aktivieren.

Danke für den Tip. Ich hatte mit -Wall kompiliert und war bisher der 
Meinung, dass im Code nichts Schwerwiegendes falsch ist, wenn bei -Wall 
keine Warnung kommt. Man lernt nie aus...

von Faultier (Gast)


Lesenswert?

@ Holger G.

>Hier macht er es also so, wie es dasteht, und pfeift auf den C-Standard.

Nein! Das tut er nicht! Der Standard sagt, das das Verhalten 
"undefiniert" ist.
Es ist wichtig, daß Du das akzeptierst.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Aber mindestens in -Wextra hätte das wohl schon hineingepasst.

Sehe ich allerdings auch so.  Sollte man einen enhancement request
dafür einreichen?

von (prx) A. K. (prx)


Lesenswert?

Ja.

von Holger G. (lutex)


Lesenswert?

Auch Ja.

von Faultier (Gast)


Lesenswert?

@ Jörg Wunsch

> Die ist aber durchaus folgenschwer.

Da gebe ich Dir durchaus recht. Aber warum schreibst Du das mir?

Es war mir garnicht bewusst, das meine Antwort von wegen 
"Unaufmerksamkeit" so zu verstehen war, als wenn ich, um Dir zu 
gefallen, eine ohnehin gleichwertige Formulierung durch eine andere 
ersetzt habe. Falls es keinen relevanten Unterschied zwischen 
"undefiniert" und "implementierungsabhängig" geben würde, dann hätte ich 
das schon geschrieben.

von Fred (Gast)


Lesenswert?

Holger G. schrieb:
> "volatile" bezieht sich auf die Vermeidung von Seiteneffekten durch
> Interrupts.

Falsch.

> Hier macht er es also so, wie es dasteht, und pfeift auf den C-Standard.

Falsch.

Dir wurde bereits erklärt, was der Standard sagt. Der gcc hält sich mit 
wie ohne Optimierung daran. Er macht sogar das, was man tendentiell 
erwarten würde.

> Und ich hätte mindestens erwartet, daß er mir eine Warnung ausgibt, wenn
> er diese eindeutig endende Schleife entfernt und einfach durch eine
> Endlosschleife ersetzt.

Ja, dann schalt die Warnung eben einfach ein. Wurde dir ebenfalls schon 
mitgeteilt, wie das geht.

Mehr noch, es gibt sogar eine Option, das von dir gewünschte Verhalten 
einzuschalten.

Was willst du eigentlich?

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Ich hab das mal fixx mit dem armcc ausprobiert bei -O0 sowie -O3, und 
der zählt brav runter und steigt bei --i == 0 aus :-)
1
volatile unsigned int xxx=0;
2
3
void foo() {
4
  xxx--;
5
}
6
7
int main()
8
{
9
  signed char i=0;
10
  
11
  while(--i) {
12
    foo();
13
  }
14
}

von (prx) A. K. (prx)


Lesenswert?

Random ... schrieb:
> Ich hab das mal fixx mit dem armcc ausprobiert bei -O0 sowie -O3, und
> der zählt brav runter und steigt bei --i == 0 aus :-)

Das ist ebenso zulässig. Undefiniertes Verhalten schliesst auch jenes 
Verhalten ein, das der Programmierer erwartet. :-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Ja.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59301

Ich habe das mal vom AVR weg verallgemeinert.  Letztlich ist das
wdt_reset() ja weiter nichts als ein asm("wdr"), und indem man da
ein asm("nop") schreibt, passt das am Ende für praktisch jedes
Target.  Damit hat man auch die Leute noch erreicht, die bei AVR im
Subject gleich weiterlesen würden. ;-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Random ... schrieb:
> und der zählt brav runter und steigt bei --i == 0 aus :-)

Mit deinem Code.  Nun benutze mal das Äquivalent zum obigem Code:
1
int main(void)
2
{
3
  int i = 0;
4
  while (--i)
5
    asm("nop");
6
  for (;;) ;
7
}

Was bekommst du?
1
.L2:
2
      nop
3
      b .L2

Also letztlich dasselbe wie oben.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Faultier schrieb:
> Aber warum schreibst Du das mir?

Nicht dir, sondern ich wollte nur allgemein nochmal den Unterschied
zwischen "undefined" und "implementation-defined" behaviour
hervorheben.  Für den Hinterkopf gewissermaßen.

von Holger G. (lutex)


Lesenswert?

Fred schrieb:
> Ja, dann schalt die Warnung eben einfach ein. Wurde dir ebenfalls schon
> mitgeteilt, wie das geht.
>
> Mehr noch, es gibt sogar eine Option, das von dir gewünschte Verhalten
> einzuschalten.
>
> Was willst du eigentlich?

Kam nur beim Schreiben nicht mit Lesen hinterher. Aber nun ist alles 
klar. Vielen Dank an alle.

von Oliver (Gast)


Lesenswert?

Jörg Wunsch schrieb:
>
1
> int main(void)
2
> {
3
>   int i = 0;
4
>   while (--i)
5
>     asm("nop");
6
>   for (;;) ;
7
> }
8
>
>
> Was bekommst du?
>
>
1
> .L2:
2
>       nop
3
>       b .L2
4
>
>
> Also letztlich dasselbe wie oben.

Was allerdings in dem Fall dem vom Pogrammierer gewünschten Verhalten 
ausreichend nahe kommt ;)

Oliver

von Holger G. (lutex)


Lesenswert?

Jörg Wunsch schrieb:
> A. K. schrieb:
>> Ja.
>
> http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59301
>
> Ich habe das mal vom AVR weg verallgemeinert.  Letztlich ist das
> wdt_reset() ja weiter nichts als ein asm("wdr"), und indem man da
> ein asm("nop") schreibt, passt das am Ende für praktisch jedes
> Target.  Damit hat man auch die Leute noch erreicht, die bei AVR im
> Subject gleich weiterlesen würden. ;-)

Danke!

von Faultier (Gast)


Lesenswert?

@ Jörg Wunsch
>Nicht dir, ...
Ach so. :-)

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Zählt auch brav runter. Bei -O0 und -O3.

-O0:
1
    64: int main(void) 
2
    65: { 
3
0x08000610 4770      BX       lr
4
    66:   int i = 0; 
5
0x08000612 2000      MOVS     r0,#0x00
6
    67:   while (--i) 
7
0x08000614 E000      B        0x08000618
8
    68:     __NOP(); 
9
0x08000616 BF00      NOP      
10
0x08000618 1E41      SUBS     r1,r0,#1
11
0x0800061A 0008      MOVS     r0,r1
12
0x0800061C D1FB      BNE      0x08000616
13
    69:   for (;;) ; 
14
0x0800061E BF00      NOP      
15
0x08000620 E7FE      B        0x08000620

von Lutz H. (luhe)


Lesenswert?

Ein unerwartetes Optimierungsverhalten war auch bei diesem Code 
festzustellen. Das durchlaufen der Schleife ist unsicher.

  do{
    if (GPIOA->IDR & 0x0001)
    {
      GPIO_ResetBits(GPIOD,GPIO_Pin_15);
    }
    else
    {
      GPIO_SetBits(GPIOD,GPIO_Pin_15);
    }
  } while (1);

von Peter II (Gast)


Lesenswert?

lutz h. schrieb:
> Ein unerwartetes Optimierungsverhalten war auch bei diesem Code
> festzustellen. Das durchlaufen der Schleife ist unsicher.
>
>   do{
>     if (GPIOA->IDR & 0x0001)
>     {
>       GPIO_ResetBits(GPIOD,GPIO_Pin_15);
>     }
>     else
>     {
>       GPIO_SetBits(GPIOD,GPIO_Pin_15);
>     }
>   } while (1);

also ich sehe nichts wo hier der optimiere zuschlagen könnte. (Unter der 
Voraussetzung das  GPIOA->IDR und GPIOD Register sind die volatile 
sind.)

Oder auf was willst du hinaus?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Random ... schrieb:
> Zählt auch brav runter. Bei -O0 und -O3.

Dann hast du einen anderen ARM-GCC als ich.  Bei mir kam das raus,
was ich oben gepostet habe.

> -O0:

-O0 brauchst du nicht posten, dabei werden sämtliche relevanten
Betrachtungen nicht angeworfen.  Es muss also mindestens -O1 sein.

von Lutz H. (luhe)


Lesenswert?

Ich bin auf der Suche für das Verhalten dieser Schleife.

Diese fragt bei stm 32 Board  eine Taste ab, und schaltet dann eine LED 
an oder aus. Bei zwei unterschiedlichen Board arbeitet diese Abfrage 
unzuverlässig.

Leider habe ich das fehlerhaft compilierte File, bei dem es nicht ging 
nicht mehr.
Der Code wurde so übersetzt, das die Abfrage nur einmal erfolgte,
oder die Schleife läuft endlos.
Möglicherweise erkennt der Compiler Endlosschleifen. Ich hoffe das 
möglicherweise noch andere Codebeispiele für optimierte Schleifen 
gefunden werden.

von Faultier (Gast)


Lesenswert?

@ Lutz

Erstens handelt es sich bei Deinem Fall um ein völlig anderes Thema. 
Hier gint es um eine Schleife die unbeabsichtigt eine endlose wurde. 
Deine aber ist absichtlich eine endlose.
Zweitens kann man schlecht ein nicht reproduzierbares Problem behandeln. 
Wie soll denn das gehen? Was erwartest Du?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

lutz h. schrieb:
> Ich bin auf der Suche für das Verhalten dieser Schleife.

Dann starte bitte einen eigenen Thread dafür.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:
> Random ... schrieb:
>> Zählt auch brav runter. Bei -O0 und -O3.
>
> Dann hast du einen anderen ARM-GCC als ich.  Bei mir kam das raus,
> was ich oben gepostet habe.

Ich schrieb armcc, ohne 'g' :-)
Darum habe ich das asm("nop"); auch gegen die CMSIS Variante __NOP(); 
getauscht.

: Bearbeitet durch User
von Mark B. (markbrandis)


Lesenswert?

Wenn ich als Programmierer auf Nummer Sicher gehen will, dann enabliere 
ich also:

-Wall
-Wextra

Und was noch alles? :-)

von Faultier (Gast)


Lesenswert?

>enabliere

WAS machst Du?

von Mark B. (markbrandis)


Lesenswert?

Faultier schrieb:
>>enabliere
>
> WAS machst Du?

Büschn Spaß.

One switch to rule them all, One switch to find them,
One switch to print the warnings and in the darkness bind them

von Faultier (Gast)


Lesenswert?

>Büschn Spaß.

Sehr spassig. Da lache ich am Wochende mal in Ruhe drüber.

von Thorsten S. (thosch)


Lesenswert?

Faultier schrieb:
>>Büschn Spaß.
>
> Sehr spassig. Da lache ich am Wochende mal in Ruhe drüber.

hmmm. Gehst du dazu in den Keller?
Heute mit dem falschen Fuß zuerst aufgestanden?

nix für ungut...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Random ... schrieb:
> Ich schrieb armcc, ohne 'g' :-)

OK, flasch verstanden, schließlich sind wir ja im GCC-Forum.  Wer auch
immer einen armcc bastelt.

von Rolf Magnus (Gast)


Lesenswert?

Mark Brandis schrieb:
> Wenn ich als Programmierer auf Nummer Sicher gehen will, dann enabliere
> ich also:
>
> -Wall
> -Wextra
>
> Und was noch alles? :-)

Minimum sind für mich grundsätzlich
1
-std=c99 (oder gnu99) -pedantic -Wall -Wextra

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:
> Wer auch immer einen armcc bastelt.

Damals(tm) wurde der ARMCC von Norcroft entwickelt und von ARM als ARM 
SDT (Software Development Toolkit) vertrieben. Um 2000 gab es die 
Umstellung auf ARM ADS, ebenfalls von Norcroft und ARM.

Mit dem Kauf von Keil durch ARM wurden jedoch die Compiler durch die 
entsprechenden Produkte von Keil ersetzt, ohne dies allzu publik zu 
machen.

von Oliver (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Minimum sind für mich grundsätzlich
> -std=c99 (oder gnu99) -pedantic -Wall -Wextra

Nutzt in dem aktuellen Fall aber auch nichts.
Und pedantic zusammen mit gnu99 ist ja nun irgendwie wiedersinning.

Oliver

von Rolf Magnus (Gast)


Lesenswert?

Oliver schrieb:
> Rolf Magnus schrieb:
>> Minimum sind für mich grundsätzlich
>> -std=c99 (oder gnu99) -pedantic -Wall -Wextra
>
> Nutzt in dem aktuellen Fall aber auch nichts.

Deshalb schrieb ich ja "Minimum". Außerdem hätte ich solchen Code auch 
nicht geschrieben. ;-)

> Und pedantic zusammen mit gnu99 ist ja nun irgendwie wiedersinning.

Ich wäre ja auch mit C99 zufrieden, aber auf dem PC werden halt manche 
POSIX-Funktionen oder z.B. so Sachen wie M_PI damit abgeschaltet, weil 
sie eigentlich nicht ISO-C-Konform sind. In dem Fall weiche ich dann auf 
GNU99 aus. Ob -pedantic dann noch sinnvoll ist, darüber hab ich ehrlich 
gesagt bisher gar nicht nachgedacht.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Andreas Schweigstill schrieb:
> Mit dem Kauf von Keil durch ARM wurden jedoch die Compiler durch die
> entsprechenden Produkte von Keil ersetzt, ohne dies allzu publik zu
> machen.

Nicht wirklich. Das MDK-ARM (also die µVision IDE) werkelt seit dem auch 
mit dem armcc. Der ursprüngliche ARM Compiler von Keil wurde 
eingestellt.

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.