Hallo!
Ich habe ein (mittlerweile gelöstes) Problem mit dem C-Compiler erlebt.
Aber vielleicht bin ich auch einfach nur zu dämlich ;-)
Ich habe folgendes am Ende der Main-Funktion stehen:
endlos: if(whattodo == 1) {
updatetime();
updatedisplay();
whattodo = 0;
};
goto endlos;
Bevor jetzt alle schreien: Das Problem, welches ich gleich beschreibe,
"funktioniert" auch mit while(1) { ... }.
Dieser Code soll in der Endlosschleife ausgeführt werden, damit auf
"whattodo" reagiert werden kann, whattodo wird in einer ISR gesetzt.
Der Compiler macht da folgendes raus:
@000002B9: endlos
49: endlos: if(whattodo == 1) {
+000002B9: 91800078 LDS R24,0x0078 Load direct from data
space
+000002BB: 91900079 LDS R25,0x0079 Load direct from data
space
+000002BD: 9701 SBIW R24,0x01 Subtract immediate
from word
+000002BE: F449 BRNE PC+0x0A Branch if not equal
50: updatetime();
+000002BF: 940E01DB CALL 0x000001DB Call subroutine
51: updatedisplay();
+000002C1: 940E0272 CALL 0x00000272 Call subroutine
52: whattodo = 0;
+000002C3: 92100079 STS 0x0079,R1 Store direct to data
space
+000002C5: 92100078 STS 0x0078,R1 Store direct to data
space
+000002C7: CFF1 RJMP PC-0x000E Relative jump
+000002C8: CFFF RJMP PC-0x0000 Relative jump
Ich bin fast vom Glauben abgefallen. Ich suche tagelang (!!) den Fehler,
und die if-Bedingung (BRNE) verzweigt einfach hinter mein GOTO! Der
Compiler hat schon irgendwie gemerkt, dass was faul ist und hat
vorsichtshalber mal noch eine eigene Endlosschleife eingebaut. Abhilfe
hat geschaffen, dass ich zwischen dem Blockende des if-Befehls und dem
Goto noch eine Codezeile geschrieben habe (z.B. Variablenzuweisung).
Dann macht der Compiler z.B. sowas:
@000002B9: endlos
49: endlos: if(whattodo == 1) {
+000002B9: 91800078 LDS R24,0x0078 Load direct from data
space
+000002BB: 91900079 LDS R25,0x0079 Load direct from data
space
+000002BD: 9701 SBIW R24,0x01 Subtract immediate
from word
+000002BE: F441 BRNE PC+0x09 Branch if not equal
50: updatetime();
+000002BF: 940E01DB CALL 0x000001DB Call subroutine
51: updatedisplay();
+000002C1: 940E0272 CALL 0x00000272 Call subroutine
52: whattodo = 0;
+000002C3: 92100079 STS 0x0079,R1 Store direct to data
space
+000002C5: 92100078 STS 0x0078,R1 Store direct to data
space
56: ampm=0;
+000002C7: 92100077 STS 0x0077,R1 Store direct to data
space
+000002C9: 92100076 STS 0x0076,R1 Store direct to data
space
+000002CB: CFED RJMP PC-0x0012
Also ist alles in Ordnung. Ich kann mir nur nicht erklären, wie es zu so
einem Verhalten kommt. Kann mir jemand ein Tipp geben?
Viele Grüße
Marcel
PS: Ich benutze das neuste AVRStudio und den neuesten WinAVR. Ich habe
für einen ATMega32 mit 8MHz compiliert.
Marcel_74 wrote:
> endlos: if(whattodo == 1) {> updatetime();> updatedisplay();> whattodo = 0;>> };>> goto endlos;
Wenn man solchen Code schreibt sollte ein vernuenftiger Compiler das
sowieso nicht mehr uebersetzen.
Um im Bild zu bleiben: Statt vom Glauben abzufallen solltest du lieber
die K&R Bibel nochmal lesen, und zwar im Abschnitt zu "volatile".
Der Compiler stellt nämlich völlig zurecht fest, dass dieser Code
spätestens nach einer Runde mit "whattodo=1" auf eine leere Totschleife
rausläuft, und optimiert die nun auf maximale Performance. Es kann nicht
damit rechnen, dass da vielleicht ein Interrupt die Variable wieder
setzt. Nicht ohne "volatile".
Einen Vorwurf kann man dem Compiler allerdings machen: Dass er den
ersten der beiden abschliessende Sprünge drin gelassen hat. Der ist
nämlich komplett überflüssig.
Hallo!
@Michael G:
1. Mein Code sah auch mal anders aus. Was Du hier siehst ist die 127.
Änderung, da ich mir das auftretende Problem nicht erklären konnte.
2. BTW: Was bitte ist an einer simplen if-Abfrage nicht mehr
"übersetzungswürdig"? Im Übrigen führen gerade beim Programmieren viele
Wege zum Ziel. Es mag elegantere Wege geben und weniger elegante, aber
das ändert nichts an der Tatsache, dass das Programm dennoch
funktioniert und auch das macht was man möchte. Andere Haltungen sind
eher arrogant als nützlich.
3. Du hast mir kein Stück weitergeholfen.
@A.K.
Vielen Dank für den Hinweis. Ich hatte schon die Vermutung, dass der
Compiler hier etwas optimiert, was sich meiner Kenntnis entzieht. Ich
bin halt "nur" Hobbyprogrammierer.
Was ich allerdings nicht verstehe, ist die Tatsache, dass "richtig"
compiliert wird, wenn noch Code zwischen dem Ende des if-Blocks und dem
Goto steht.
Schade finde ich an diesem Mikrocontroller-Forum allerdings immer noch
(im Gegensatz zu manch anderen Foren), dass es immer Leute gibt, die
schlaue Sprüche machen und sich für die Helden des Programmierens halten
und den Fragestellen echt keinen Millimeter weiterhelfen. Zum Glück gibt
es Leute wie A.K., die kurz und bündig helfen! Danke!
Viele Grüße
Marcel
> Was ich allerdings nicht verstehe, ist die Tatsache, dass "richtig"> compiliert wird, wenn noch Code zwischen dem Ende des if-Blocks und dem> Goto steht.
Na A. K. hats doch schon gesagt: Informier dich mal über "volatile".
Der Compiler weiss nicht, dass dein whattodo in einer isr geändert wird
und nimmt deshalb an, dass der Code im if-Block nur einmal ausgeführt
werden kann. Mit volatile, kannst du dem Compiler sagen, dass die
Variable ihren Wert "unvorhersehbar" ändern kann. Dann wird er solche
Optimisierungen unterlassen.
Warscheinlich weil man goto nicht ohne Not einsetzen sollte, wegen des
Stils ;P
1
while(1){
2
if(whattodo==1){
3
updatetime();
4
updatedisplay();
5
whattodo=0;
6
};
7
}
tut das von dir gewünschte :)
Wenn du Anweisungen einfügst, dann kommt der Compiler ggf zu anderen
Annahmen bzgl der Relevanz deines Codes.
Vieleicht wäre es hilfreich die Variablendefinition mit anzugeben. Und
ein Beispiel geht/geht nicht, das man sieht WELCHE anweisung genau
eingefügt wurden.
Marcel_74 wrote:
> Was ich allerdings nicht verstehe, ist die Tatsache, dass "richtig"> compiliert wird, wenn noch Code zwischen dem Ende des if-Blocks und dem> Goto steht.
Und wenn er dann mal das tut was du von ihm willst bist du auch wieder
nicht glücklich. ;-)
Im ersten Fall steht zwischen dem =0 und der Abfrage davon kein Code der
irgendwas verändern könnte. Im zweiten Fall befürchtet er möglicherweise
eine Identität der beiden Variablen (aliasing) - was zwar dank des
gleichen Wertes egal wäre, aber trotzdem den Optimierungstest aushebelt.
Hallo!
Ja, ich habe mich mittlerweile über volatile informatiert. Gute Sache
sowas ;-) Mir wäre es ja fast lieber, wenn der Compiler nicht so stark
mitdenkt. Im Grunde habe ich mir ja etwas (nicht so ganz abwegiges)
gedacht.
Und um die leidliche Programmierstildiskussion nicht allzu ausufern zu
lassen: In meinem ersten Beitrag habe ich doch schon beschrieben, dass
die goto-Variante schon eine Verzweiflungstat war. Tatsächlich wurde
vorher mit while(1) gearbeitet. Im Übrigen war die ganze Sache
ursprünglich noch komplizierter: Ich teste einzelne Bits in einem Byte,
ob sie gesetzt sind oder nicht. Das hat den Hintergrund, dass meine ISR
verschiedene "Kommandos" an das Hauptprogramm absetzen kann, um es zu
verschiedenen Aktionen zu bewegen. Und DAS ist imho guter Stil ;-)
Viele Grüße und herzlichen Dank für die hilfreichen Kommentare
Marcel
Marcel_74 wrote:
> Ja, ich habe mich mittlerweile über volatile informatiert. Gute Sache> sowas ;-) Mir wäre es ja fast lieber, wenn der Compiler nicht so stark> mitdenkt.
Anfangs gab es das in C-Compilern auch nicht. War nicht nötig. Bis weit
in der 80er Jahre haben viele C-Compiler weitgehend das gemacht, was in
der Zeile drin stand. Erst als die Compiler anfingen, auch über
Statement-Grenzen hinweg zu optimieren, statt Registeroptimierung dem
Programmierer zu überlassen, wurde das wichtig (hast du dich mal
gefragt, warum es in C ein Keyword "register" gibt?).
Manch andere Compiler, vor allen solche die auf Microcontroller
spezialisiert sind, sind bei derartiger Optimierung und Umordnung von
Code nicht so agressiv. Sei es weil eine Akkumulator-Architektur wie
68xx,PIC,8051 dafür wenig Raum lässt oder fehlendes tiefes Pipelining es
einfach unnötig macht, sei es weil es manchen Programmierern übel
aufstösst ;-). Der GNU-Compiler hingegen muss sich mit Compilern für
High-Performance-Computing messen und optimiert dementsprechend
agressiv. Und einiges davon merkt man auch in der AVR-Version, und es
gereicht dort auch nicht immer zum Vorteil.
Hallo!
Vielen Dank für die Hintergrundinfos. Aber selbst wenn ich vorher über
"volatile" gestolpert wäre, hätte ich zumindest in diesem Fall nicht
wirklich daran gedacht es zu benutzen. Aber jetzt weiß ich worauf ich
achten muss.
Gibt es eine Übersicht über die Art der Optimierungen, die der Compiler
vornimmt? Woher soll ich wissen was kritisch ist und was nicht? In
meinem speziellen Fall gab es noch nicht einmal eine Warnung, obwohl
sonst wirklich alles angemakelt wird.
Viele Grüße
Marcel
Marcel_74 wrote:
> Gibt es eine Übersicht über die Art der Optimierungen, die der Compiler> vornimmt? Woher soll ich wissen was kritisch ist und was nicht?
Falscher Ansatz. Richtig programmieren heisst die Devise. Und um dich
vor sowas warnen zu können, müsste er mehr wissen als er wissen kann.
Das ist ja grad der Witz dran: erst "volatile" sagt ihm das.
Regel: Der Compiler weiss nichts über Interrupt-Routinen, d.h. auch wenn
es im Quelltext anders aussieht, weiss er nicht dass timer_interrupt()
eine Interrupt-Routine ist.
Hallo!
Ich Grunde weiß doch der Compiler, dass ich eine Variable in einer
if-Bedingung teste, die im Verlaufe des Programms (genauer: main) nicht
verändert wird. Ich werde ja auch gewarnt, wenn ich eine Variable
daklariere und diese anschließend nicht verwende.
Aber Du hast dennoch recht: Wenn ich wieder mehr in der Materie stecke,
werde ich auch wieder besser programmieren. Hinzugelernt habe ich ja
bereits ;-)
Viele Grüße
Marcel
Betreffs Compiler und Optimierung.
Ich habe da eine Software auf einem Atmega644p die über 2 serielle
Schnittstellen Protokolle verschiedener Geräte konvertiert.
Das ist ein ziemlich vergriesgnaddeltes Ding mit dynamischem Speicher
und doppelt verketteten Listen, als Interrupts laufen Timer und die
Sende- und Empfangsinterrupts der beiden UARTs.
Ich habe 2 Notebooks, ein ältliches mit W2K und einem 550er Professor,
auf dem die Applikation ursprünglich geschrieben wurde und ein etwas
neueres, mit 1,6GHz/1GB RAM. Auf letzterem habe ich ein neueres WinAVR
installiert gehabt, ich glaube die letzte Version, auf dem alten Laptop
eine 20071221
oder so.
Auf dem neuern Notebook machte die Software wirklich nur noch quatsch
wenn
der Compiler mit -Os optimierte und verhielt sich völlig anders im
Debugger (Jtag Ice MkII) als ohne. Nachdem ich mich 3 Tage lang mit
Schattenboxen beschäftigt habe habe ich "J" mal zu dem WinAVR Versionen
befragt.
Aussage so in etwa " ich habe da bei dem Bugs etwas die Übersicht
verloren, duch den Xmega Import sind da wohl etliche Fehler rein
gekommen, benutze 20071221".
Mir sind volatile Variablen durchaus bekannt und mein Code enthält IMHO
Nichts, über was der Compiler bei Optimierungen stolpern sollte.
Mit dem älteren WinAVR sind die Probleme wie weg geblasen, Debugger
und RUN sind konsistent...
Also meine Empfehlung für die "Standard Atmegas": benutzt den älteren
Compiler...
Ehe der Hinweis kommt "malloc benutzt man bei Embedded nicht":
Mir bleibt nichts Anderes übrig. Ich muß jede Menge Strings
analysieren und umsortieren da reicht der Platz nur mit dynamischem
Speicher.
Hallo Atmel: Ich brauche mehr RAM, und von wegen RAM ist teuer;
ich bezahle den auch!!!
Gruß,
Holm
Marcel_74 wrote:
> Ich Grunde weiß doch der Compiler, dass ich eine Variable in einer> if-Bedingung teste, die im Verlaufe des Programms (genauer: main) nicht> verändert wird. Ich werde ja auch gewarnt, wenn ich eine Variable> daklariere und diese anschließend nicht verwende.
Das ist richtig. Vielleicht gibt's auch eine Warnung die man extra dazu
einschalten kann.
Allerdings ist das mit den Warnungen so eine Sache. Wenn man damit zu
weit geht, dann hat jedes normale Programm so viele ganz normale
Warnungen, dass man die ernsten übersieht.
So war es früher in Compilern durchaus üblich, auf Code wie
if (1 == 2)
lautstark hinzuweisen. Die Devise war, dass der Anwender doch den
Präprozessor verwenden soll. Das hat sich allerdings geändert, seit der
Präprozessor etwas schlecht angesehen ist und umgebungsabhängige
Fallunterscheidungen in übersichtlicher Weise direkt dem Compiler
überlassen werden.
Im Makefile steht dann beispielsweise
-DVERSION=1
und im Code
if (VERSION == 2)
...
was man traditionell aber unübersichtlich mit
#if VERSION == 2
gemacht hat.