Hallo,
in einem meiner Fachbücher "AVR-RISC" (Mittermayr) schlägt der Autor am
Ende von main eine Endlosschleife vor;
...
while (1); // Endlosschleife "hang-up"
return 0;
}
Soll einen uneindeutigen Programmzustand, nach Abschluss verhindern.
Macht das in der Praxis wirklich Sinn?
__Son´s Bersi__ schrieb:> Macht das in der Praxis wirklich Sinn?
Nein.
Die Mainloop ist ja schon endlos.
Alles dahinter ist unerreichbarer Code.
Mit Optimierung wird er auch nicht erzeugt.
Peter
__Son´s Bersi__ schrieb:> Hallo,> in einem meiner Fachbücher "AVR-RISC" (Mittermayr) schlägt der Autor am> Ende von main eine Endlosschleife vor;>> ...> while (1); // Endlosschleife "hang-up"> return 0;> }>> Soll einen uneindeutigen Programmzustand, nach Abschluss verhindern.> Macht das in der Praxis wirklich Sinn?
Der Grundaufbau JEDES µC Programmes sieht immer so aus
Wie sinnvoll es ist, hinter die sowieso obligate Endlosschleife in
main() noch mal eine Endlosschleife als Paranoia-Beruhiger zu setzen -
nun, diese Frage kannst du dir sicherlich selbst beantworten.
> ... nach Abschluss verhindern.
ein µC Programm hat keinen 'Abschluss'. Das Programm läuft vom Anlegen
der Versorgungsspannung an den µC, solange bis du ihm den Saft abdrehst.
Was soll denn passieren, wenn main() jemals verlassen wird? main() wird
nie verlassen! Die Haupschleife IST eine Endlosschleife. Der µC kommt da
nie wieder raus.
__Son´s Bersi__ schrieb:> Soll einen uneindeutigen Programmzustand, nach Abschluss verhindern.> Macht das in der Praxis wirklich Sinn?
Naja ^^
Wie definierst du Sinn machen?
Es tut natürlich das was du gesagt hast:
Es versetzt den µC in eine Endlosschleife, so dass er nichts anderes
mehr tut.
(Aber aufpassen, dass die leere Schleife nicht irgendwie wegoptimiert
wird)
Andererseits:
Der Compiler sollte das, wenn du in C programmierst selbstständig machen
soweit ich informiert bin. Wenn die main() abgeschlossen wurde darf der
µC nicht Ammok laufen, deshalb wird auf Assemblerebene wohl sowieso eine
Schleife gesetzt (von der du nur nichts mitbekommst), als Absicherung.
In Assembler müsstest du dich natürlich selbst darum kümmern.
Von dem her ist das ganze wohl eher eine doppelte Absicherung, dass auch
wirklich nichts passiert.
Ansonsten:
Es ist für µC Programe ja gar nicht üblich, dass sie nur ein mal laufen.
main() sollte gar nicht auslaufen.
Im Normalfall besteht so ein Programm ja immer aus der Struktur
Das kommt drauf an was man möchte - einen kontinuierlich arbeitenden µC,
meistens bei fertigen Projekten, oder nur einen einzigen
Programmdurchlauf wie bei Hallo-Welt-Versuchen.
In meinen Programmen ist oft eine hangup-Funktion vorhanden, die im
Fehlerfall angesprungen werden kann und die Hardware in einen sicheren
Zustand bringt (alles aus und Ruhe). Wenn noch ein Controller-Pin dafür
frei war bekommt der auch eine Status-LED, über die man dann einen
Fehlercode ausgeben kann.
Ben _ schrieb:> In meinen Programmen ist oft eine hangup-Funktion vorhanden, die im> Fehlerfall angesprungen werden kann
Dann steht die aber nicht im Main.
Hier ging es um das Main und da ist es Unsinn, toten Code hinein zu
schreiben.
Peter
Peter Dannegger
> Ben _ schrieb>> In meinen Programmen ist oft eine hangup-Funktion vorhanden, die im>> Fehlerfall angesprungen werden kann>> Dann steht die aber nicht im Main.> Hier ging es um das Main und da ist es Unsinn, toten Code hinein zu> schreiben.
Vor allen Dingen ist es ja auch logisch sinnlos :-)
Wenn der µC es irgendwie schafft, aus der Hauptschleife auszubrechen
(Magie? Eingriff eines Erzengel? Ausserirdische? Uri Geller?), dann
schafft er es auch, aus der zweiten Emdlosschleife rauszukommen.
der hung-up ist im main()!
int main(void)
{
...
while (1); // Endlosschleife "hang-up"
return 0;
}
Zitat des Autors;
"Um sicher zu gehen, dass sich Ihr Programm nach der Ausführung nicht in
einen unbestimmten Zusatand begibt, empfiehlt es sich auch, diese
Endlosschleife am Ende jedes Programms zu verwenden."
In der Parxis sinnvoll oder überflüssig bis unsinnig?
Brauchst du nicht! Wenn du dein Programm gescheit schreibst, wirst du
nie da hin kommen. Und wenn doch, sollte man sich auf Fehlersuche machen
und nicht solch unschöne lösungen verwenden.
ABER: Eine Endlsschleife in der main() brauchst du, selbst wenn diese
selbst leer ist. Sonst wird dein Programm immer wieder von vor anlaufen.
__Son´s Bersi__ schrieb:> In der Parxis sinnvoll oder überflüssig bis unsinnig?
Du solltest das so lange verwenden, bis du selbst weisst, wann du es
nicht brauchst.
__Son´s Bersi__ schrieb:> In der Parxis sinnvoll oder überflüssig bis unsinnig?
Hast du die Antworten eigentlich überhaupt gelesen?
__Son´s Bersi__ schrieb:> Zitat des Autors;> "Um sicher zu gehen, dass sich Ihr Programm nach der Ausführung nicht in> einen unbestimmten Zusatand begibt, empfiehlt es sich auch, diese> Endlosschleife am Ende jedes Programms zu verwenden."
Wie sieht denn ein typisches Programm von der Stelle des Buches an der
du im Moment bist (Ich vermute mal ganz am Anfang) denn aus?
Ich könnte mir nur vorstellen, dass er sich momentan noch auf einmalige
Vorgänge beschränkt und deshalb die Endlosschleife einbaut.
Also irgendwas in die Richtung:
1
intmain(void{
2
setzeLed();
3
while(1);
4
}
Später wird diese dann vermutlich gefüllt und somit implizit zur
obligatorischen Endlosschleife, die das Kernstück eines jeden
µC-Programm's darstellt.
Das sollte dir aber auch klar werden, wenn du
1. die Antworten oben ließt
2. im Buch weiter ließt
Wenn der Author wirklich hinter die Hauptschleife noch eine leere setzt
ist das eher zweifelhaft ^^
In der Praxis spielt sich der wesentliche Teil des Programms in genau
dieser Endlosschleife (oder in Interrupts) ab!
Alles vor der Endlosschleife wird nur genau einmal nach dem Einschalten
des Mikrocontrollers ausgeführt. Da steht also nur die einmalige
Initialisierung, d.h. die Richtung von Ports festlegen, Timer
einstellen, Interrupts aktivieren usw.
Danach läuft das Programm in die Endlossschleife und arbeitet dort seine
Aufgaben ab. Es schaut also z.B. nach, ob Daten per UART empfangen
wurden und bearbeitet sie gegebenenfalls. Das macht es einfach so lange,
bis man den Strom abschaltet.
In manchen Fällen kann man die Endlosschleife auch leer lassen, dann
spielt sich das komplette Programm nur in Interrupts ab. Außerdem kann
man am Ende der Endlosschleife den Mikrocontroller in den Sleep-Mode
versetzen, um Strom zu sparen. Dann macht er einfach nichts, bis er
durch ein externes Ereignis wieder aufwacht. Sobald er aufwacht,
durchläuft er die Endlosschleife erneut und legt sich nach Abarbeitung
aller Aufgaben wieder schlafen.
Ein Programm ohne Endlosschleife macht dagegen nicht viel Sinn. Der
Mikrocontroller würde nur einmal nach dem Einschalten etwas machen und
was danach passiert wäre "undefiniert". Vielleicht erzeugt der Compiler
von alleine eine Endlosschleife, vielleicht springt er wieder an den
Anfang, vielleicht durchläuft er einfach weiter den Flashsspeicher und
führt aus, was kommt ... Selbst wenn das Programm also wirklich nur
einmal nach dem Einschalten "Hello World" ausgeben und danach tot sein
soll, sollte man dieses "tot sein" explizit in Form einer leeren
Endlosschleife hinschreiben. Denn irgendetwas muss der Controller am
Ende des Programms immer machen.
Nach einer vorhandenen Endlosschleife nochmal eine leere Endlosschleife
zu schreiben, ist allerdings sinnlos. Vermutlich wird das in dem Buch
aber auch nicht vorgeschlagen, sondern bezieht sich auf ein allererstes
Programm, das wirklich nur einmal etwas tut.
Dominik S. schrieb:> (Aber aufpassen, dass die leere Schleife nicht irgendwie wegoptimiert> wird)
Kann nicht passieren. Außer der Compiler hat einen Bug, aber dann kann
alles mögliche passieren.
Davis schrieb:> Dominik S. schrieb:>>> Also irgendwas in die Richtung:>> int main(void {>> setzeLed();>> while(1);>> }>> So hat der Autor des obigen Buches es gemeint.
Dann sollte er das auch als das bezeichnen, was es ist. So ist das
einfach nur 'Schwachsinn', denn ein typisches µC Programm sieht nun mal
nicht so aus. Ausser bei den allerersten Programmen, die nichts anderes
tun als einmalig eine LED einzuschalten.
Ein kurzer Absatz über den typischen Programmaufbau, mit der Erklärung
dass es sich hier beim LED-Einschalten um eine Einmalaktion handelt, die
deshalb vor die Hauptschleife gezogen wird und jeder kennt sich aus.
Aber so ist das mit dem Rumgerede über 'uneindeutige Programmzustände
nach Abschluss' einfach nur verwirrend bis missverständlich.
__Son´s Bersi__ schrieb:> "Um sicher zu gehen, dass sich Ihr Programm nach der Ausführung nicht in> einen unbestimmten Zusatand begibt
Wenn man schon so paranoid ist, sollte man gleich per Assembler den
Programmspeicher bis zum physikalischen Ende mit NOP oder JMP 0 oder JR
$ auffüllen.
Andrerseits ist das (das "nach der Ausführung") bereits ein unbestimmter
Zustand bzw. ein ungewollter.
Gruss Reinhard
Im übrigen ist hier auch das int bei main ziemlich überflüssig.
Eine Funktion die niemals endet hat sicher keinen sinnvollen
return-Wert, also beaucht ein Compiler auch gar nicht versuchen für eine
aufrufende Instanz (beim PC das Betriebssystem, und beim Controller???)
Platz für einen Rückgabewert im RAM oder Stack zu reservieren.
Beim Controller also
1
voidmain(void)// manche Compiler meckern, wenn da kein void drin steht
2
{
3
// Einmalig ausgeführte Befehle
4
while(1)
5
{
6
// wiederholte Ausführung
7
}
8
// hier kommt der Controller niemals an, also auch nicht
9
// zu return xyz;
10
}
Ebenso sinnfrei ist int main(Parameterliste)... es gibt beim Controller
ja niemand, der das Programm aufruft, also kann auch nie ein
Übergabeparameter drin stehen.
Karl Heinz Buchegger schrieb:> Davis schrieb:>> Dominik S. schrieb:>>>>> Also irgendwas in die Richtung:>>> int main(void {>>> setzeLed();>>> while(1);>>> }>>>> So hat der Autor des obigen Buches es gemeint.>> Dann sollte er das auch als das bezeichnen, was es ist.
Wozu noch aufregen?
Bernhard Spitzer schrieb:> Im übrigen ist hier auch das int bei main ziemlich überflüssig.> Eine Funktion die niemals endet hat sicher keinen sinnvollen> return-Wert, also beaucht ein Compiler auch gar nicht versuchen für eine> aufrufende Instanz (beim PC das Betriebssystem, und beim Controller???)> Platz für einen Rückgabewert im RAM oder Stack zu reservieren.
dann ist es aber kein C
Vlad Tepesch schrieb:> dann ist es aber kein C
Auf was soll sich "es" beziehen? Im zitierten Absatz sind über 10
Substantive. Etwas klarere Kommentare wären dann vielleicht auch
wirklich sinnvoll. Oder um es mit Dieter Nuhr zu sagen...
Andreas B. schrieb:> Kann nicht passieren. Außer der Compiler hat einen Bug, aber dann kann> alles mögliche passieren.
Was immer wieder unterschätzt wird: Der Programmierer ist eine viel
größere Fehlerquelle als der Compiler. Gerade die "aber in der
Praxis..."-Rufer sollten das wissen. Selbst bei einem formalen
Korrektheitsbeweis (was wohl bis auf in speziellen Situationen eh keiner
tut) kann man Fehler machen.
So eine Endlosschleife verhindert einen undefinierbaren Zustand, wenn
man so dämlich war und doch irgendwie für ein Verlassen der
Hauptschleife gesorgt hat. Dann kann man nämlich vernünftig debuggen,
weil alle lokalen Variablen noch erreichbar sind.
Ich geh sogar noch einen Schritt weiter und pack ans Ende keine nackte
Endlosschleife sondern irgendwas, was mir ne LED blinken lässt oder so,
damit man auf den ersten Blick sieht, dass man Mist gebaut hat. Nichts
ist frustrierender als stundenlang über seltsamem Verhalten zu grübeln,
bis man merkt, dass das blöde Ding sich eigentlich nur die ganze Zeit
wegen eines Programmfehlers resettet.
Reinhard Kern schrieb:> Wenn man schon so paranoid ist, sollte man gleich per Assembler den> Programmspeicher bis zum physikalischen Ende mit NOP oder JMP 0 oder JR> $ auffüllen.
Sowas bietet sich für Programme an, die in den Regelbetrieb gehen.
Natürlich muss das Programm darauf ausgelegt sein, jederzeit resettet
werden zu können, ohne dass irgendwas komisches passiert. Das ist
vergleichbar mit BOD/WDT, nur für eine andere Fehlerquelle.
Bernhard Spitzer schrieb:> Auf was soll sich "es" beziehen? Im zitierten Absatz sind über 10> Substantive. Etwas klarere Kommentare wären dann vielleicht auch> wirklich sinnvoll. Oder um es mit Dieter Nuhr zu sagen...
du referierst doch den ganzen Absatz, darüber, dass du den
int-Rückgabewert doof findest und ihn weglassen willst. Was werde ich
also meinen?
Coder schrieb:> Wie oben schon gesagt wurde. Einewhile(1)> {> // Hier kommt das Hauptprogramm> }> verlässt man auf normalen Wege nicht und wenn doch landet man vermutlich> nicht in der hinten angestellten Schleife.
und wenn man irgendwo ein break drin hat, dessen while oder for man
wegeditiert hat, weils ebend doch keine schleife brauchte, sondern nur
ein if?
@Vlad Tepesch
Meine Haupt while-schleife gestalte ich uebersichtlich, in der Form
1
while(1)
2
{
3
Aufagbe1();
4
Aufgabe2();
5
Aufgabe3();
6
}
ausserdem ist ein break; kein schönes zur Ablaufsteuerung, ausser beim
Switch-Case Konstrukt.
Normalerweise gibt der Compiler Warnung bzgl. unerreichbaren Codes; Wenn
er aber dieses break doch erreichbar sein sollte müsste die Warnung doch
verschwinden?!?
Ich hab nicht gesagt, dass es schön ist, aber einen möglichen Fall
aufgezeigt, wo es doch passieren könnte.
Bei sauberer Programmierung sollte es nicht passieren.
Jeder hat eine andere Ansicht von sauberer Programmierung. Ich bin eher
defensiv und strikt eingestellt und vermeide wenn möglich
fehlerträchtige Konstrukte :-)
Sam P. schrieb:> Andreas B. schrieb:>> Kann nicht passieren. Außer der Compiler hat einen Bug, aber dann kann>> alles mögliche passieren.>> Was immer wieder unterschätzt wird: Der Programmierer ist eine viel> größere Fehlerquelle als der Compiler. Gerade die "aber in der> Praxis..."-Rufer sollten das wissen. Selbst bei einem formalen> Korrektheitsbeweis (was wohl bis auf in speziellen Situationen eh keiner> tut) kann man Fehler machen.
?? Darum ging es nicht. Ich habe geschrieben, dass der Compiler eine
leere Endlosschleife nicht wegoptimiert, außer sie wird ohnehin nie
erreicht. Darauf kann man sich verlassen. Wenn nicht, kann man sich auf
gar nichts verlassen.
Ah, dann habe ich deinen Beitrag falsch verstanden/zitiert. Was das
gesagte nicht schmälert. Das genannte "break"-Beispiel ist genau so ein
Fall. Gerade bei kniffligen Problemen, irgendwelchen Timing-Problemen
mit externen Komponenten oder so, wo man schon seit Stunden dran
rumwerkelt, weil Theorie und Praxis wieder mal nicht übereinstimmen,
passiert es einem, dass man "nur mal eben zum Ausprobieren" etwas
einfügt, ändert, teilweise/unvollständig löscht, und BAM... ist die
Endlosschleife so endlos gar nicht mehr. Da ist es schon praktisch,
einen definierten Fehlerzustand zu haben.
Aus einer bekannten Headerdatei für Pic Programmierung:
#define b asm("nop") //convenient point for breakpoint (debugging)
#define l while(1) //loop for ever (debugging)
auch wenn ich klein l niemals verwenden würde, L könnte ok sein, ein
h oder H für halt wäre besser, i und l wie auch j und k sind bei mir für
loops in for/while und als sehr temporäre Variablen reserviert.
Zudem verwende ich gerne l für strlen wenn dies in derselben Funktion
öfter gebraucht werden.
Für debug wird b umdefiniert, einen Aufruf einer debug Funktion, auf der
ein Breakpoint gesetzt wird, ein Toggeln eines Pins, eine Ausgabe
der aktuellen Proogrammadresse auf der seriellen Schnittstelle oder
SPI, ... ,
Ein Vorschlag:
#define brk() asm("nop") //convenient point for breakpoint (debugging)
#define halt() while(1) brk() //loop for ever (debugging)
und beim Debug brk undefinieren und diese Funktion schreiben,
aber man könnte auch b und h verwenden, den Bezeichner l finde ich zu
gefährlich.
Karl Heinz Buchegger schrieb:> Wenn der µC es irgendwie schafft, aus der Hauptschleife auszubrechen> (Magie? Eingriff eines Erzengel? Ausserirdische? Uri Geller?), dann> schafft er es auch, aus der zweiten Emdlosschleife rauszukommen.
Da reicht ein *break;* und schon isser raus. SW-Fehler gibt es doch
immer. ;-)
Und dann isses gut wenn man das abfängt.
Sam P. schrieb:> So eine Endlosschleife verhindert einen undefinierbaren Zustand, wenn> man so dämlich war und doch irgendwie für ein Verlassen der> Hauptschleife gesorgt hat. Dann kann man nämlich vernünftig debuggen,> weil alle lokalen Variablen noch erreichbar sind.
Ein weiter Vorteil ist, das die Peripherie nicht irgendwelche
unkontrollierten Dinge tut.
Karl Heinz Buchegger schrieb:> Der Grundaufbau JEDES µC Programmes sieht immer so aus> int main()> {> falls Interrupts benutzt werden: sei();> while( 1 ) {>> }> }
Nicht immer. Schau die Beispiele zum MSP430 an. Anstatt endless loop
wird der µC in einen Low Power Mode versetzt. Den behält nach der
Bearbeitung einer ISR bei. Es wird kein Befehl mehr im Hauptprogramm
ausgeführt. ;-)))
16 Bit schrieb:> Karl Heinz Buchegger schrieb:>> Wenn der µC es irgendwie schafft, aus der Hauptschleife auszubrechen>> (Magie? Eingriff eines Erzengel? Ausserirdische? Uri Geller?), dann>> schafft er es auch, aus der zweiten Emdlosschleife rauszukommen.> Da reicht ein *break;* und schon isser raus. SW-Fehler gibt es doch> immer. ;-)>> Und dann isses gut wenn man das abfängt.
Wenn du dich wirklich irrtümlich aus der Hauptschleife rausbreakst, dann
kannst du dich auch der Default-Endlossschleife im Runtime System
anvertrauen, in die ein AVR beim gcc einläuft, wenn er jemals aus main()
zurückkommt.
Endlosschleife mit abgeschalteten Interrupts - diese Fehlersituation ist
garantiert nicht zu übersehen.
DANKE für die zahlreichen Rü´s!
Die Letzten schweiften mir allerdings zu weit von meinem Thema ab.
Die Kontroversen zeigen mir, dass es sich eher um eine Grundeinstellung
handelt. Zusätzliche "hang-up" für zusätzliche Sicherheit, wenn die
main() nur ein mal durchlaufen wird.
__Son´s Bersi__ schrieb:> Die Kontroversen zeigen mir, dass es sich eher um eine Grundeinstellung> handelt. Zusätzliche "hang-up" für zusätzliche Sicherheit, wenn die> main() nur ein mal durchlaufen wird.
Die Sicherung für die Sicherung.
Irgendwann ist es genug. Wenn man aus der Hauptschleife rauskommt, dann
ist das ein definitiv ein Fehler. Der gehört korrigiert und nicht
abgesichert.