Forum: Mikrocontroller und Digitale Elektronik bug in optimizer von avr-gcc?


von hans dieter (Gast)


Angehängte Dateien:

Lesenswert?

hallo,
ich arbeite gerade an einem teil der firmware für einen roboter. Nun
bin ich gerade auf eine Stelle gestoßen, die mir zu denken gibt.
Folgendes Problem: Ich habe in der main.c ein paar Variablen um eine
FSM zu beschreiben.
Dazu existiert noch eine Init-Funktion, die Anfangswerte setzt:
(siehe main.c im Anhang)
//...
unsigned char stateWalkNext;
//...
int main(void)
{
  initSMs();
//...
  stateWalkNext = STWALK_ROTR;
}
//...
void initSMs(void)
{
  stateWalkNext = STWALK_FORW;
}

So nun zum Problem; Wenn ich die Optimierungen einschalte - egal welche
Stufe - dann funktioniert die Initialisierung zwar noch, jedoch wird
dann die Zuweisung in der main-Funktion wegoptimiert.
Erst wenn ich die Variable volatile mache - was eigentlich nicht nötig
sein dürfte - taucht die Zuweisung bei aktivierter optimierung wieder
auf.
Habe ich da einen Denk-Fehler oder ist das eine Bug im Optimierer von
AVR-GCC?

von A.K. (Gast)


Lesenswert?

Daumenregel: 99,9% aller hier als Compiler-Bugs vermuteten Probleme sind
nicht dem Compiler zuzuschreiben, sondern beruhen auf Miss/Unverständnis
seitens des Anwenders. Und darauf, dass der GNU-Compiler aufgrund seiner
Herkunft weit mehr Optimierungsmöglichkeiten erkennt, als bei
Mikrocontrollerncompilern sonst üblich (und gewünscht).

Hier: Einige bis alle Funktionsaufrufe in main() werden inlined. d.h.
nicht aufgerufen sondern deren Code in main direkt erzeugt. Das erlaubt
dem Compiler weitere Optimierungen, Darunter ist die Erkenntnis, dass
bei zweifacher Zuweisung an die gleiche Variable, ohne Funktionsaufruf
dazwischen, die erste überflüssig ist. Erst "volatile" sagt teilt ihm
mit, davon Abstand zu nehmen.

von hans dieter (Gast)


Lesenswert?

was ich noch vergessen habe (bevor fragen kommen)
ich nutze die 3.4.3 (20050214) release von avr-gcc

von A.K. (Gast)


Lesenswert?

Andere Perspektive: Interrupts existieren für einen Compiler nicht. Dass
eine Funktion mitten drin durch einen Interrupt unterbrochen werden kann
ist ihm egal, er nimmt darauf keine Rücksicht. Daher muss der
Programmierer jedwede Datem, die sowohl in Hauptprogramm als auch in
Interruptroutinen verwerdet werden, mit "volatile" verzieren.

von Peter D. (peda)


Lesenswert?

Ja, volatile ist schon ein schwieriges Thema, ich hab mich noch immer
nicht richtig daran gewöhnen können.

Der Keil C51 war stillschweigend davon ausgegangen, daß man sich was
dabei gedacht hat, eine Variable global anzulegen, da mußte ich nie
volatile verwenden.

Auch kann man leider nicht nach volatile casten. D.h. auch der
Interrupt kann eine solche Variable nicht im Register behalten, um Code
zu sparen, obwohl ihm die ja keiner unterm Hintern wegziehen kann.

Da kann dann ein Trick besser optimierten Code erzeugen:
Die Variable wird nicht als volatile deklariert (Interrupt kann nun
optimieren) und dafür in der Mainloop immer über eine Funktion gelesen.



Peter

von A.K. (Gast)


Lesenswert?

"und dafür in der Mainloop immer über eine Funktion gelesen"

Dann aber bitte diese Funktion so deklarieren, dass sie keinesfalls
inlined wird, oder inlining ganz abschalten, sonst wird das auch wieder
abhängig vom Wasserstand.

von Rolf Magnus (Gast)


Lesenswert?

> Der Keil C51 war stillschweigend davon ausgegangen, daß man sich
> was dabei gedacht hat, eine Variable global anzulegen,

Das tut auch der GCC. Aber er weiß ja nicht, daß man bei einer
speziellen Variable daran gedacht hat, diese auch in Interrupts zu
verwenden.

> da mußte ich nie volatile verwenden.

> Auch kann man leider nicht nach volatile casten. D.h. auch der
> Interrupt kann eine solche Variable nicht im Register behalten, um
> Code zu sparen, obwohl ihm die ja keiner unterm Hintern wegziehen
> kann.

Das kann man ja einfachst selbst machen. Kopiere sie am Anfang der
Interrupt-Routine in eine lokale Variable und am Ende wieder zurück.

Übrigens: wenn der Keil das so macht, wie du oben sagst, dann würde er
so eine Optimierung ja grundsätzlich niemals machen können.

> Da kann dann ein Trick besser optimierten Code erzeugen:
> Die Variable wird nicht als volatile deklariert (Interrupt kann
> nun optimieren) und dafür in der Mainloop immer über eine Funktion
> gelesen.

Das ist aber nicht zuverlässig.

von A.K. (Gast)


Lesenswert?

"Der Keil C51 war stillschweigend davon ausgegangen, daß man sich was
dabei gedacht hat, eine Variable global anzulegen"

Anders ausgedrückt: Der Optimizier ist nicht so weit entwickelt ;-).
Und diese Transparenz ist bei Controllern ja durchaus erwünscht. Der
GCC entstammt aber gänzlich anderen Sphären, die Verwendung für
Controller ist da nur ein Nebeneffekt.

"volatile" gibt es erst seit ANSI-C, K&R-C kennt das nicht. Die
Fortschritte der Compiler-Technik in den 80ern machten das nötig. Bei
älteren Compilern hörte Optimierung oft schon an den Statement-Grenzen
auf, da war sowas überflüssig (dafür war "register" mitnichten
überflüssig).

von Unbekannter (Gast)


Lesenswert?

@hans:

Der Compiler hat schon recht.

Denn wenn Deine Init-Funktion nur am Anfang von main aufgerufen wird,
und danach nicht wieder, kann Deine Init-Funktion die globale Variable
ja nicht weiter ändern, also kann der Compiler in main mit dieser
Variablen machen, was er will.

Wie hier schon angemerkt: Wenn die globale Variable von einem Interupt
verändert werden kann, musst Du das mit volatile dem Compiler sagen.
Für alle anderen Fälle, in denen Du kein Interrupt verwendest, brauchst
Du das nicht und kannst Dich darauf verlassen, dass der Compiler schon
weiß, was er da macht.

Um in der Interruptroutine die volatile-Veriable nicht ständig lesen zu
müssen (bei komplizierten Berechnungen) kannst Du folgendes machen:

  volatile int globale_variable;

  void interrupt_funktion()
  {
    int kopie = globale_variable;

    // komplizierte Berechnungen mit 'kopie'
    // Noch mehr Berechnungen mit 'kopie'

    globale_variable = neuer_wert;
  }


  void initailisierung()
  {
    globale_variable = irgendetwas;
  }


  int main()
  {
    initialisierung();

    for(;;)
    {
       int andere_kopie = globale_variable;

       // mache irgendetwas mit 'andere_kopie'
       // noch mehr Berechnungen mit 'andere_kopie'

       globale_variable = ganz_neuer_wert;
    }

    return 0;
  }


Einfach dem Compiler vertrauen. Der macht das schon. Und nur für
Variablen die von interrupt-Funktionen gelesen oder geschrieben werden,
volatile verwenden. Für alle anderen kein volatile benutzen.

von Christian Zietz (Gast)


Lesenswert?

Im vorliegenden Fall, optimiert der Compiler die Zuweisung wohl weg,
weil sie die letzte Anweisung von main ist. Da der Compiler ja nicht
wissen kann, dass die Variable von später stattfindenden Interrups noch
benötigt wird, sieht er die Zuweisung als unnötig an. Das Programm ist
für ihn ja mit dem Ende von main beendet und der neue Wert der Variable
wird somit nicht mehr benötigt.

von A.K. (Gast)


Lesenswert?

Was die Optimierung angeht, ist main() eine ganz normale Funktion. Dass
ein Programm am Ende von main() endet, interessiert ihn nicht. Schon
weil es nicht stimmt [exit() Code und sonstige Aufräumaktivitäten].

Freilich wird hier main() überhaupt nicht beendet und das ist grad der
Witz daran. Das Hauptprogramm besteht jenseits der Initialisierung nur
aus der for-Schleife, und da diese Schleife nie endet und niemanden
aufruft, ist für den Compiler niemand in Sicht, der etwas mit globalen
Variablen anfangen kann.

Ändert man das Programm so, dass aus Sicht des Compilers main() beendet
werden kann, dann steht die Zuweisung wieder drin.

Korrektur: Mit inlines hat das hier nichts zu tun.

von Christian Zietz (Gast)


Lesenswert?

Stimmt, ich habe doch glatt die for-Schleife übersehen. A.Ks Erklärung
ist richtig.

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.