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.
Hallo! Dumme Frage jetzt: Wozu braucht(e) man eine Konstruktion wie if (1==2) ? Viele Grüße Marcel
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.
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.