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.
@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!!!
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.
>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...
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.
Eine eigene Frage?
Dieser Thread ist über 13 Jahre alt.
Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.