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?
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.
was ich noch vergessen habe (bevor fragen kommen) ich nutze die 3.4.3 (20050214) release von avr-gcc
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.
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
"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.
> 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.
"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).
@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.
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.
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.