vllt. eine dumme Frage: was passiert wenn die main-schleife einmal durchlaufen ist? wird nur noch auf Interrupts reagiert? resettet sich der uC? tut er einfach gar nichts? Danke für sinnvolle Antworten
fooo schrieb: > tut er einfach gar nichts? da du ja nicht schreibst bei welchen µC gehe ich mal von einem 8bit Atmel aus. Nach der main kommt einfach eine Endlosschleife wo vorher noch die Interrupts abgeschaltet werden. Es passiert also nichts mehr.
>was passiert wenn die main-schleife einmal durchlaufen ist?
Welche? Zeigen.
@Peter tut mir leid, das ich das nicht ergänzt habe. war ne allgemeine Frage, aber 8-bit benutze ich häufig. @holger die Frage ist wohl rein akademisch. ich habe eigentlich nicht vor, so etwas zu schreiben. Mich hat ein Freund danach gefragt.
fooo schrieb: > vllt. eine dumme Frage: > was passiert wenn die main-schleife einmal durchlaufen ist? was ist mit main-schleife gemeint? Generell durchläuft eine Schleife so oft wie die Bedingung der Schleife erfüllt ist (s. C-Grundlagen). Beispiel-Code zeigen, dann kann man besser darauf eingehen!!!
nachtrag: mir ist schon klar, dass so eine schleife nicht endet:
1 | int main() |
2 | {
|
3 | init(); //muss ja nicht sein |
4 | while(true)//oder for(;;) |
5 | {
|
6 | //...
|
7 | }
|
8 | return 0; |
9 | }
|
fooo schrieb: > ir ist schon klar, dass so eine schleife nicht endet: Ein break geht immer :-) Normalerweise wird der exit-Code ausgeführt und das Betriebssystem übernimmt die Kontrolle.
Hängt stark vom Compiler, der libc, dem Startupcode, etc. ab. allgemein kann man annehmen dass dann irgendetwas zufälliges (undefiniertes) passiert, vom einfachen Absturz bis zum Ausführen des Codes der zufälligerweise hinter der main im Speicher steht...
@fooo
>mir ist schon klar, dass so eine schleife nicht endet:
Wolltest Du Dich nur mal in Scene setzen, oder warum fragst Du
überhaupt?
Das Beispiel ist ja mehr als nur selbsterklärend.
also lieber eine leere while(true)-Schleife schreiben(wenn sie nicht wegoptimiert wird), dann ist das Problem aus der Welt? OK. Gut Danke
>mir ist schon klar, dass so eine schleife nicht endet:
Warum fragst Du dann?
Das Problem fängt, wie hier schon angedeutet, damit an, das es soetwas
wie eine main-Schleife strenggenommen nicht gibt. Aber wenn
erfahrerenere Leute den Audruck benutzen, dann ist im allgemeinen die
von Dir beschriebene Endlos-Schleife gemeint. Für diese macht Deine
Frage keinen Sinn, denn die Schleife wird ja nie verlassen. Es gibt also
kein "nach der Schleife".
Andererseits kann eine Schleife, wenn man will und das so ausdrückt,
auch enden. Dann eben kommt es auf die Laufzeitumgebung und/oder ein
evtl. OS und den uC an, was konkret geschieht.
Allgemein kann man die Frage nicht beantworten.
fooo schrieb: > also lieber eine leere while(true)-Schleife schreiben(wenn sie nicht > wegoptimiert wird), dann ist das Problem aus der Welt? Ja, die schreibt man immer, und nein die wird nie wegoptimiert, denn das würde ja das Verhalten des Programms ändern.
Dr. Sommer schrieb: > Hängt stark vom Compiler, der libc, dem Startupcode, etc. ab. Der Compiler hat nur mittelbar was damit zu tun. > allgemein > kann man annehmen dass dann irgendetwas zufälliges (undefiniertes) > passiert, Etwas undefiniertes passiert nicht. > vom einfachen Absturz bis zum Ausführen des Codes der > zufälligerweise hinter der main im Speicher steht... Mir sind bisher folgende Varianten begegnet: - main() wird erneut ausgeführt - Endlosschleife Schau im Startup-Code nach, wie es bei Dir gelöst ist. Heinrich
> Der Compiler hat nur mittelbar was damit zu tun. Stimmt, aber der evtl. mitgelieferte Startupcode, C-Library, etc. > Etwas undefiniertes passiert nicht. Doch, undefiniert von C (oder C++) - definiert nur von einer Menge praktisch zufälliger Umstände (zB wie ld die Funktionen anordnet). > Mir sind bisher folgende Varianten begegnet: > - main() wird erneut ausgeführt > - Endlosschleife > > Schau im Startup-Code nach, wie es bei Dir gelöst ist. Mein Startupcode nimmt einfach an das main() nicht zurückkehrt. Kehrt sie doch zurück, wird das ausgeführt was danach im Flash steht - irgendeine Funktion, Konstante, 0en... Allerdings wird der Controller schon beim Rückkehren selber ("b lr") crashen (MemFault Handler oder so), da der Stack bereits leer ist.
fooo schrieb: > nachtrag: > > mir ist schon klar, dass so eine schleife nicht endet: > [c] > int main() > { > init(); //muss ja nicht sein > while(true)//oder for(;;) > { > //... > } > return 0; > } Das ist keine Schleife, es ist eine Funktion! Vergleiche Legende von der "If-Schleife": http://www.if-schleife.de/
So, mal sehen, was ANSI (Committee Draft — September 7, 2007 ISO/IEC 9899:TC3) sagt: 5.1.2.2.1 Program startup 1 The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters: int main(void) { /* ... */ } or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared): int main(int argc, char *argv[]) { /* ... */ } 5.1.2.2.3 Program termination 1 If the return type of the main function is a type compatible with int, a return from the initial call to the main function is equivalent to calling the exit function with the value returned by the main function as its argument;10) reaching the } that terminates the main function returns a value of 0. If the return type is not compatible with int, the termination status returned to the host environment is unspecified. Forward references: definition of terms (7.1.1), the exit function (7.20.4.3). Allerdings sieht es im embedded Bereich zuweilen etwas anders aus.
LassWonadersDefinen schrieb: > So, mal sehen, was ANSI (Committee Draft — September 7, 2007 ISO/IEC > 9899:TC3) sagt: Trifft allerdings nur auf den hosted mode zu. Der wird in der embedded-Welt nicht immer benutzt (wenngleich man ihn beim AVR-GCC gern benutzt, weil er bestimmte bibliotheksbezogene Optimierungen ermöglicht). > Allerdings sieht es im embedded Bereich zuweilen etwas anders aus. Für den freestanding mode trifft das alles nicht zu. Weder muss die initial aufgerufene Funktion main() heißen, noch wird vom Standard verlangt, dass der Rückkehrwert von main() an exit() übergeben werden muss.
Um zu wissen, was passiert, wenn das main() verlassen wird, muss man erst mal wissen, wie der Prozessor ins Main reinkommt. So klar, wie das jetzt scheint, ist das nämlich nicht. Der Prozi macht nach Power-Up nämlich genau etwas (je nach Typ ein bisschen unterschiedlich (Cortex-M3 z.B. macht ein bissche was anderes), aber im Wesentlichen immer gleich): Er schaut bei seinem Reset-Vektor (z.B. Adresse 0 oder so), was da für Befehle stehen und führt diese aus. Aber was steht denn da? NICHT main(), sondern der sog. Startup-Code, oft crt0 genannt. Da passieren dann so Dinge wie z.B. initialisierung von globalen Variablen, Aufsetzten der MMU (falls vorhanden), des Oszillators, Aufruf von Konstruktoren globaler Objekte (falls C++) etcetc... Und ganz am Schluss kommt dann ein Call (nicht Jump) ins Main(). Die korrekte Antwort lautet also: Er geht zurück ins CRT0. Und was da hinter dem main() drin steht, ist Deine Entscheidung, resp. die Entscheidung des Autors von CRT0. Und das kannst Du auch leicht nachschauen. Üblich ist z.B: eine Endlosschaufe mit einem Kommentar dahinter: "Should never get there" oder so. Oder auch einfach gar nichts. Dann macht er, was immer hinten für Befehle stehen. Wenn der Linker das main() hinten drangeklatscht hat, ist er wieder drin, wenn dahinter erst mal leerer Bereich kommt, führt er vielleicht erst mal 1000mal den Befehl mit der Codierung 0xff aus (was immer der macht)..... etcetc. Gruäss Simon
Simon Huwyler schrieb: > Und ganz am Schluss kommt dann ein Call (nicht Jump) ins Main(). Warum muss das ein Call sein? Damit verschwendet man doch Stack-Space und Prozessor-Zyklen. Ein Jump ist effizienter und funktioniert genauso gut unter der Annahme, dass die main() nie zurückkehrt, was sie onehin nie tun sollte...!
Dr. Sommer schrieb: > Warum muss das ein Call sein? Weil die Laufzeitumgebung eben nicht garantieren kann, dass main() nie zurückkehrt, denn main() liegt in der Domain des Programmierers, und der Compiler verwehrt es diesem nicht, da ein return drin zu haben. Klar, man kann sich natürlich hinstellen: „Das hier ist ein freestanding environment, main() darf nicht zurückkehren, alles andere löst undefiniertes Verhalten aus.“ Da aber der Aufruf von main() nur zwei oder vier Byte im Stack braucht (für die Rückkehradresse), die man auf fast jedem heutigen Controller vernachlässigen kann, ist es guter Ton, dass man statt undefiniertem Verhalten bei Rückkehr aus main() eben lieber definiert in eine Endlosschleife läuft.
Dr. Sommer schrieb: >> Und ganz am Schluss kommt dann ein Call (nicht Jump) ins Main(). > Warum muss das ein Call sein? Damit verschwendet man doch Stack-Space > und Prozessor-Zyklen. Ein Jump ist effizienter und funktioniert genauso > gut unter der Annahme, dass die main() nie zurückkehrt, was sie onehin > nie tun sollte...! aber viele wollen halt ein Definiertes Ende haben und nicht ein undefiniertes. Damit muss man ein Call machen, damit man danach eine Endlosschleife erzeugen kann.
Jörg Wunsch schrieb: > Weil die Laufzeitumgebung eben nicht garantieren kann, dass main() > nie zurückkehrt, denn main() liegt in der Domain des Programmierers, Speicherverwaltung mit Pointer-Hantiererei liegt auch in der Domain des Programmierers, dennoch hindert einen die runtime nicht daran in unallokierten Speicher zu schreiben... Jörg Wunsch schrieb: > und der Compiler verwehrt es diesem nicht, da ein return drin zu > haben. Wenn man den Prototypen der main als "noreturn" [1] deklariert, schon. Jörg Wunsch schrieb: > nur zwei oder vier Byte im Stack braucht Und C-Programmierer sind die, die über die angebliche Ineffizienz von C++ oder Java meckern, schludern aber ein Word weg nur für den Fall dass der Programmierer es nicht schafft while(1); zu schreiben. Peter II schrieb: > Damit muss man ein Call machen, damit man danach eine > Endlosschleife erzeugen kann. Oder den Programmierer zwingen, die Endlosschleife selber zu machen (effizienter). 1: Bjarne Stroustrup, The C++ Programming Language, Fourth Edition, S. 314, §12.1.7
Dr. Sommer schrieb: > Peter II schrieb: >> Damit muss man ein Call machen, damit man danach eine >> Endlosschleife erzeugen kann. > Oder den Programmierer zwingen, die Endlosschleife selber zu machen > (effizienter). dann schaffen es bestimmt einige ein return reinzuschreiben.
Dr. Sommer schrieb: > Warum muss das ein Call sein? Damit verschwendet man doch Stack-Space > und Prozessor-Zyklen. Und wie kommst du auf so eine Behauptung? Alles das hängt nämlich vom konkreten Prozessor und vom konkreten Startupcode ab. z.B.hier IMPORT main LDR R0, =main BX R0 wird weder der Stack benutzt noch das RP Register gesetzt. Ein Rücksprung würde also unweigerlich im Nirvana landen und bestenfalls ne Exception liefern. W.S.
W.S. schrieb: > wird weder der Stack benutzt noch das RP Register gesetzt. Ein > Rücksprung würde also unweigerlich im Nirvana landen Richtig, also ist das kein Funktionsaufruf (kein bl), die CRT hat keine Möglichkeit hier gegen versehentliches Rückkehren zu sichern, und kein Stackspeicher wird verschwendet. Genauso wie es sein sollte. Wieso du das als "Call" ansiehst ist mir nicht ganz klar. W.S. schrieb: > Und wie kommst du auf so eine Behauptung? Weil ein Funktionsaufruf immer (ob direkt über die Call-Instruktion, oder indirekt über "push lr" in der aufgerufenen Funktion) Stack-Speicher für die Rücksprungadresse benötigt. Das ist quasi die Charakteristik einer Funktion auf Instruktionen-Ebene. Alles andere ist ein Sprung... Peter II schrieb: > dann schaffen es bestimmt einige ein return reinzuschreiben. Nicht ohne Compilerfehler dank "noreturn" (s.o.). Wer C,C++ nicht korrekt bedient kriegt (und dafür gibt es einiges an Gelegenheit, versehentlich zurückkehrende main() ist da noch harmlos) sollte vielleicht lieber python o.ä. benutzen (läuft zB. auf dem R-PI, geht also auch embedded).
W.S. schrieb: > Und wie kommst du auf so eine Behauptung? > Alles das hängt nämlich vom konkreten Prozessor und vom konkreten > Startupcode ab. > z.B.hier > IMPORT main > LDR R0, =main > BX R0 > > wird weder der Stack benutzt noch das RP Register gesetzt. Der Stack wird nicht in crt0 verschwendet, sondern in der main() dir LR sichert um selbst Funktionsaufrufe zu machen. Was sich aber mit noreturn abschalten lassen sollte...
Marc P. schrieb: > Was sich aber mit noreturn > abschalten lassen sollte... Das war die Idee, leider hat der GCC damit so seine Probleme... noch ;-)
Dr. Sommer schrieb: > Oder den Programmierer zwingen Die Laufzeitumgebung hat keine Chance, den Programmierer zu irgendwas zu zwingen. Wenn sie abgearbeitet wird, kann der Programmierer bereits über die 7 Berge bei den 7 Zwergen sein. ;-) Aber es steht dir in jedem Falle ja frei, deinen eigenen Startupcode zu benutzen, wenn dir die paar Bytes derart wichtig sind. Selbst bei closed-source-Compilern liegt der in der Regel als Sourcecode für den Anwender modifizierbar irgendwo vor.
Jörg Wunsch schrieb: > Die Laufzeitumgebung hat keine Chance, den Programmierer zu > irgendwas zu zwingen. Korrekt, das kann sie aber auch sonst auch nirgendwo, daher kann man es auch gleich lassen. Der Compiler kann den Programmierer aber sehr wohl zwingen bzw. darauf hinweisen dass er die main() bitteschön nicht verlassen möge. Der praktisch nicht vorhandenen Laufzeitsicherheit von C,C++ wird durch die recht hohe Compiler-Sicherheit begegnet. Jörg Wunsch schrieb: > wenn dir die paar Bytes derart wichtig sind. Es geht mehr ums Prinzip, ein paar Bytes kostbaren Stackspeicher für die ganze Laufzeit des Programms völlig sinnlos zu verschwenden, nur für den Fall dass der Programmierer eventuell vergessen hat while(1); zu verwenden.
Dr. Sommer schrieb: > nur für > den Fall dass der Programmierer eventuell vergessen hat while(1); zu > verwenden. Ich denke, das ist jetzt halt ein bisschen eine Philosophiefrage. while(1) kann man nicht bloss vergessen zu verwenden, man kann es auch ganz bewusst nicht verwenden. Dann nämlich, wenn man z.B. ein PC-Programm schreibt. Und es wurde nun mal genau definiert, wie sich ein main() zu verhalten hat. Dass es beendet werden kann, dass es einen int zurückgeben muss etc. Und sobald man im Main() ist, möchte man halt möglichs plattformunabhängig sein. Also muss man sich an alle Konventionen halten. Und ein Teil des Jobs von crt0 ist, die Umgebung entsprechend vorzubereiten, dass sich das C-Programm so verhält, wie sich ein C-Programm zu verhalten hat. Beispiel: Was macht eine globale Deklaration mit Zuweisung int x=5? Sie reserviert im RAM Speicher für ein x und schreibt im Flash irgendwo eine 5 hin. Das ist alles. Ohne crt0 wird diese 5 nie ins RAM geschrieben, und x hat nach Power-up irgendeinen zufälligen Wert. Sobald man im main() ist, geht man aber einfach stillschweigend davon aus, weil das in C so definiert ist. Genauso, wie man stillschweigend annehmen können muss (was für ein Satz...), dass man das Main auch wieder beenden kann. Und dann crt0 (so quasi stellvertretend für das Betriebssystem) bestimmt, was jetzt zu tun ist. Aber Du hast schon recht. Es ist eine Speicherverschwendung. Aber eben: Du kannst das crt0 dahingehend verändern, dass "die Sprache C" nicht mehr das macht, was sie per definition tun sollte. Das ist dann Deine Entscheidung, und vermutlich wird das auch oft so gemacht. Gruäss Simon
Naja, ich sage hier mal, daß die Programmierung eines µC eben anders ist als die eines Anwendungsprogrammes auf dem PC und genau deshalb ne Menge logischer Unstimmigkeiten auftreten - das ist genauso wie ein Multiuser-BS auf ner Einuser-Hardware. Das Prinzip all dieser "Steine des Anstoßes hier" ist, daß etwas, das in einer bestimmten Umgebung sinnvoll und angemessen ist, auch für Umgebungen benutzt wird, wofür es eben nicht erdacht wurde. Allerdings ist das alles, was hier diskutiert wird, ganz gewiß gar kein Thema für den vernünftigen Programmierer, sondern eben was für die Hobby-Logiker unter uns. Klar: main ist eine Funktion und Funktionen kehren zurück mit einem Funktionsergebnis - im Allgemeinen, aber eben nicht auf nem µC und schon garnicht bei einem Startupcode, der keine Returnadresse liefert. abgesehen davon... Es gibt naoch einige andere Fallstricke hier: z.B. bei einigen Systemen ne Funktion abort, die ohne passende Vorkehrungen im Startupcode ebenfalls im Nirvana endet. Oder die tollen Platzhalter in den üblichen Startupcodes bei ARM's für die diversen System-Exceptions, die alle in einem B . enden. Ganz klasse, wenn bei einem blöden Fehler das ganze System einfriert. Ich mach das inzwischen so, daß solche Exceptions nen Fehlercode setzen und dann das ganze System neu starten. Dann bleibt der µC wenigstens nicht stecken und kann reagieren. Schönen Abend noch und jammert nicht über 4 vergeigte Bytes auf dem Stack. W.S.
Simon Huwyler schrieb: > Und sobald man im Main() ist, möchte man halt möglichs > plattformunabhängig sein. Ich möchte mal ein plattformunabhängiges C-Standard-konformes Programm sehen das standalone ohne OS auf einem µC und auf einem PC läuft, (#ifdef AVR ... #else ... #endif zählt natürlich nicht) und dabei auch noch etwas sinnvolles tut, sagen wir, per system() ein paar Programme aufzurufen. Simon Huwyler schrieb: > dass "die Sprache C" nicht > mehr das macht, was sie per definition tun sollte. Tut sie bei Kernel- oder OSloser (µC-)Programmierung onehin nicht. fopen, malloc, opendir, scanf, etc. funktionieren alle nicht immer so gut auf einem µC. Genauso wie man die main() leicht abwandeln kann... W.S. schrieb: > Ganz > klasse, wenn bei einem blöden Fehler das ganze System einfriert Hä? Die Exceptions sind dazu da dass man den Fehler erkennt und den jeweiligen Prozess beendet. Tritt der Fehler im Kernel auf (bzw im standalone-Programm) ist das eben ein schwerer Fehler und man muss auf drastische Maßnahmen wie Reset zurückgreifen... Freu dich doch über den definierten Exit-Zustand. MCU ohne solche Exceptions machen dann einfach igendwas unvorhersehbares... Aus einigen der Exceptions kann man auch einfach zurückkehren, man muss sich nur überlegen was an der jeweiligen Programmstelle dann passieren soll...! W.S. schrieb: > bleibt der µC wenigstens nicht stecken Steckenbleiben wäre gar nicht so schlecht, so kann man im Debugger sehen wo es schiefgelaufen ist. Tritt dein Fehler im "Produktions"-Code auf und du verwendest den Reset um das System am laufen zu halten solltest du deinen Code reparieren...
Dr. Sommer schrieb: > dabei auch noch etwas sinnvolles tut, sagen wir, per system() ein paar > Programme aufzurufen. Es gibt durchaus mehr sinnvolle Dinge, als nun ausgerechnet per system() einen neuen Prozess starten zu wollen. Sicher, irgendwo wird es zwischen der PC-Version und der Controller-Version eine Unterscheidung geben. Das kann per #ifdef sein, aber es kann auch dadurch sein, dass die zugrunde liegenden Bibliotheksfunktionen anders arbeiten. Für Testzwecke schreibe ich übrigens durchaus auch Controllerprogramme, die ganz regulär aus dem main() rausgehen und eben nicht die typische Endlosschleife haben (weil es mir für den Test genügt, wenn alles genau einmal ausgeführt wird). Allerdings weiß ich auch, dass das verwendete Laufzeitsystem eben dann selbst den Prozessor (virtuell) anhält.
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.