Forum: Mikrocontroller und Digitale Elektronik Was passiert, wenn die main Funktion verlassen wird?


von Andreas (Gast)


Lesenswert?

Eine kurze Frage: Wenn ich die main Funktion per return 0 verlasse, 
werden dann auch alle Ausgänge zurückgesetzt (auf low) und alle 
Interrupts angehalten?

von Kein Name (Gast)


Lesenswert?

Kommt auf den Compiler, bzw. die Library an. Wenn du einen Debuger hast 
- einfach im Assember-Modus weiter steppen.

von Andreas (Gast)


Lesenswert?

Erstmal vielen Dank für deine Antwort!
Also würde im Allgemeinen ein "return 0;" nicht reichen, um alles zu 
stoppen und alle Ausgänge auf 0 zu setzen?

PS: Ich benutzte den AVR/GNU C++ Compiler (mit AtmelStudio). Kann man da 
eine Aussage treffen?

von Amateur (Gast)


Lesenswert?

Natürlich interessiert niemanden um welches Betriebssystem es sich 
handelt!

Fenster interpretiert den Rückgabewert 0 als alles palletti. Räumt dann 
auf (Ressourcen) und wartet auf besseres Wetter.

Bei Mikroprozessoren, mit Betriebssystem (Linux und Konsorten) ist ein 
ähnliches Verhalten zu erwarten, wie bei Fenster.

Bei Mikroprozessoren, ohne Betriebssystem (Linux und Konsorten) ist dies 
nicht vorgesehen.
Ich könnte mir aber zwei Szenarien vorstellen.
1. Da kein Rücksprung und damit keine Rücksprungadresse auf dem
   Stack vorhanden ist, erfolgt ein Ausflug ins Nirwana.
2. Der Compiler hat eine Dummy-Adresse auf dem Stack abgelegt, so
   gibt es zwei weitere Möglichkeiten:
   a) Ein Sprung zum Reset-Punkt - meist 0000
   b) Ein Sprung in eine Endlosschleife um irgendwelche "Irrtümer"
     zu vermeiden.

Noch etwas zum Ausflug ins Nirwana. Oft landet der Prozessor in 
Bereichen oberhalb des genutzten Speichers. Manchmal stehen hier Reste 
von früheren Programmierversuchen. Was dann passiert, ist nicht 
vorhersehbar.
Auf jeden Fall wird im Nirwana nichts gemacht. Also ein Port behält 
seinen Status, ein Hardware-Timer tickt weiter. Es kann sogar sein, dass 
aus dem Nirwana heraus, via Interrupt, etwas Sinnvolles gemacht wird, um 
dann wieder "zurückzuspringen".
FLASH-Speicher enthält, wenn er nicht programmiert wird, den Wert 0xFF. 
Was der Prozessor hieraus macht hängt von der jeweiligen Interpretation 
dieses Wertes ab. Ist diese "harmlos", so durchläuft der Prozessor 
diesen Bereich ohne viel Unsinn anzustellen und landet, wenn er "hinter" 
das Ende gerät, wieder bei 0000. Letzteres ist oft der Reset-Punkt.

von Jost .. (jojocp)


Lesenswert?

Hallo,

Schau dir mal den startup-code an. Denn die main() Funktion ist garnicht 
das erste was auf dem controller läuft. Irgendwo gibt es sicherlich ein 
bisschen Asm Code der dir deinen Controller initialisiert und 
anschließend erst in dein eigentliches Programm springt. Und wenn du 
daraus wieder rausspringst, bist du wieder im startup code...


Grüße

von Blinky (Gast)


Lesenswert?

Wenn Du wirklich den Prozessor final anhalten willst (Er würde dann erst 
nach einem Ab- und Einschalten wieder loslaufen) wäre es besser alle 
Timer, Interupts und den Watchdog sauber zu stoppen, die Ausgänge auf 0 
zu setzen und danach in einen entsprechenden Sleepmode (Falls vorhanden) 
oder eine Endlosschleife zu springen.

von Axel S. (a-za-z0-9)


Lesenswert?

Andreas schrieb:
> Eine kurze Frage: Wenn ich die main Funktion per return 0 verlasse,
> werden dann auch alle Ausgänge zurückgesetzt (auf low) und alle
> Interrupts angehalten?

Nein. Und nein.

Bei einem embedded System gibt es nichts, was nach main() noch passieren 
könnte. Die meisten Umgebungen die ich gesehen habe, packen nach den 
Aufruf von main() einfach eine Endlosschleife.

Andreas schrieb:
> Also würde im Allgemeinen ein "return 0;" nicht reichen, um alles zu
> stoppen und alle Ausgänge auf 0 zu setzen?

Warum sollte es das? Wenn du genau weißt, daß du das haben willst, dann 
schreib es doch einfach explizit an das Ende von main(). Schreib eine 0 
auf alle Ports, die Ausgangspins haben. Schalte die Interrupts entweder 
global oder per individuellem Freigabebit ab. Und dann pack da eine 
Endlosschleife hin.

von Martin (Gast)


Lesenswert?

"Auf jeden Fall wird im Nirwana nichts gemacht. Also ein Port behält
seinen Status, ein Hardware-Timer tickt weiter. "

Wenn im Nirvana nicht grade der Opcode steht, der die Ports auf Ausgang 
bratzt oder den Timer anhält.....

von Bastler (Gast)


Lesenswert?

Bei avr-gcc kommt nach main() exit(), was aus cli() und Endlosschleife 
besteht.

von nga (Gast)


Lesenswert?

Beim avr is es so:
Die Ports behalten ihren Zustand, und der Compiler erzeugt "exit"-Code. 
D.h.
 - Er baut ein cli ein, schaltet also die Interrupts ab
 - Er erzeugt eine Endlosschleife

Der AVR mach also nichts mehr außer sich langweilen...

PS: ich weiß noch wie das mit dem watchdog gemacht wird...

von Andreas (Gast)


Lesenswert?

Bastler schrieb:
> Bei avr-gcc kommt nach main() exit(), was aus cli() und Endlosschleife
> besteht.

Vielen Dank! Genau das wollte ich wissen.

von Amateur (Gast)


Lesenswert?

@Martin

>Wenn im Nirvana nicht grade der Opcode steht, der die Ports auf Ausgang
>bratzt oder den Timer anhält.....

Es kann manchmal nicht schaden einen Post ganz zu lesen...

... sonst hättest Du bemerkt, dass ich auch das folgende geschrieben 
habe:

"Manchmal stehen hier Reste von früheren Programmierversuchen. Was dann 
passiert, ist nicht vorhersehbar".

von Yalu X. (yalu) (Moderator)


Lesenswert?

Bastler schrieb:
> Bei avr-gcc kommt nach main() exit(), was aus cli() und Endlosschleife
> besteht.

Nur der Vollständigkeit halber (falls der TE einen uralten GCC benutzen
sollte): Das CLI vor der Endlosschleife kam erst mit GCC 4.3. Seit wann
es die Endlosschleife gibt, weiß ich nicht, aber mindestens seit GCC
4.2.

von Paul B. (paul_baumann)


Lesenswert?

>Was passiert, wenn die main Funktion verlassen wird?

Nun, die Funktion wird erst mal tief traurig sein -so wie jeder Andere 
auch, wenn er verlassen wird.

MfG Paul

von Heinz L. (ducttape)


Lesenswert?

Gibt es ein Leben nach der main()?

Im Computer mit OS, ja. Da kontrolliert ja das OS was davor und danach 
passiert. Im Endeffekt wird (halbwegs gutes OS vorausgesetzt) einfach 
aufgeräumt und gut is.

Im µC kommt's auf den Compiler an. Ist der compiler clever, wird er am 
Ende einen jmp auf die eigene Adresse legen. Ist er nicht ganz so clever 
endet das Programm eben und läuft in den "leeren" Programmspeicher 
dahinter, was je nach Prozessor eben etwas anderes ist. Meines Wissens 
ist leer bei AVR ein nop (no opcode), entsprechend läuft das Ding dann 
einfach leer bis zum Ende des Speichers, woraufhin entweder der IP 
überläuft und eh wieder bei Null (=Reset) anfängt oder auf eine 
ungültige Adresse zeigt und dann eben per Absturz zum Reset kommt.

Um jetzt auch die Frage zu beantworten: Zurückgesetzt wird da per 
default nix. Nur wenn das Ding halt "unkontrolliert" rumrennt wird 
irgendwann ein Reset erfolgen.

: Bearbeitet durch User
von Helmut S. (helmuts)


Lesenswert?

Andreas schrieb:
> Bastler schrieb:
>> Bei avr-gcc kommt nach main() exit(), was aus cli() und Endlosschleife
>> besteht.
>
> Vielen Dank! Genau das wollte ich wissen.


Und was ist mit dem Watchdog falls der an ist, da passiert dann doch 
noch etwas denn der kümmert sich nicht um cli().

Es ist somit völlig sinnlos aus der main() hearuszuspringen ohne vorher 
gezielt alle Funktionen in den gwünschten Zustand zu bringen. 
Genaugenommen ist es überhaupt sinnlos aus main() herauszuspringen.

von Ulrich F. (Gast)


Lesenswert?

> Und was ist mit dem Watchdog falls der an ist,
Der macht sein Ding!
Was soll er sonst tun .....

Und wenn der auf "Reset" konfiguriert ist, dann gibts ein Leben nach der 
main().
Quasi eine Wiedergeburt.

> Genaugenommen ist es überhaupt sinnlos aus main() herauszuspringen.
Jau!

Und falls es wirklich Fälle gibt, wo das einen Sinn geben mag, dann 
sollte man überlegen, ob man nicht besser ein Netzteil mit Shutdown 
Funktion verwendet. Zweckes Stromsparen.
Die PCs und ihre Netzteile machen es vor.

von Christian K. (the_kirsch)


Lesenswert?

Wird auf einem AVR die main() beendet, werden alle Interrupte 
deaktiviert und eine Endlosschleife (eine JMP-Anweisung auf sich selbst) 
läuft bis zum Hardwarereset weiter.


Ist der Watchdog aktiviert, erzeugt der Watchdog nach Ablauf seines 
Timers ein Reset.

von Axel S. (a-za-z0-9)


Lesenswert?

Helmut S. schrieb:
> Es ist somit völlig sinnlos aus der main() hearuszuspringen ohne vorher
> gezielt alle Funktionen in den gwünschten Zustand zu bringen.
> Genaugenommen ist es überhaupt sinnlos aus main() herauszuspringen.

Genau so ist das. Ich würde sogar so weit gehen und es einen Bug nennen, 
wenn main() in einer embedded Anwendung verlassen werden kann.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Andreas schrieb:
> Eine kurze Frage: Wenn ich die main Funktion per return 0 verlasse,
> werden dann auch alle Ausgänge zurückgesetzt (auf low) und alle
> Interrupts angehalten?

Der C-Standard sagt:
1
5.1.2.2.3 Program termination
2
If the return type of the main function is a type compatible with
3
int, a return from the initial call to the main function is
4
equivalent to calling the exit function with the value returned
5
by the main function as its argument; reaching the } that terminates
6
the main function returns a value of 0. If the return type is not
7
compatible with int, the termination status returned to the host
8
environment is unspecified.
Die Aussage, nach main lande ein C- oder C++-Progamm im "Nirvana", 
entbehrt also jeder Grundlage.

Bei Verwendung der avr-libc fällt _exit auf exit. Bei avr-gcc sind _exit 
und exit in der libgcc definiert, und exit ist dort weak:
1
  .section .fini9,"ax",@progbits
2
DEFUN _exit
3
  .weak  exit
4
exit:
5
ENDF _exit
6
  ;; Code from .fini8 ... .fini1 sections inserted by ld script.
7
  .section .fini0,"ax",@progbits
8
  cli
9
__stop_program:
10
  rjmp  __stop_program
main wiederum wird per [R]CALL vom Startup-Code aufgerufen.

Im Standard-Layout werden die .finiN-Section — so vorhanden — innerhalb 
von exit abgearbeitet, und dort werden dann Dinge erledigt wie:

- Ausführung von statischen C++ Destruktoren

- Ausführung von C-Destruktoren

- Ausführung von mit per atexit() registrierten Funktionen

Zugegebenermaßen wurde die Behandlung von statischen C++ Destruktoren 
und atexit-Funktionen erst kürzlich überarbeitet und vervollständigt, 
denn diese Features werden wohl kaum verwendet.

Bei Verwendung eines Simulators kann es aber ganz praktisch sein, und es 
gibt auch Testsuites wie die von GCC welche entsprechende Features 
testen.

Hier zum Beispiel die Implementierungen von exit und abort von avrtest, 
dem Simulator, mit dem avr-gcc getestet wird:
1
/* This defines never returning functions exit() and abort() */
2
3
/* Postpone raising EXIT until .fini1 below so that higher .fini dtors,
4
   destructors and functions registered by atexit() won't be bypassed.  */
5
6
static int avrtest_exit_code;
7
8
/* libgcc defines exit as weak.  */
9
10
void exit (int code)
11
{
12
  __asm volatile ("sts %0+0,%A1"  "\n\t"
13
                  "sts %0+1,%B1"  "\n\t"
14
                  "%~jmp _exit"
15
                  : /* no outputs */
16
                  : "i" (&avrtest_exit_code), "r" (code));
17
  for (;;);
18
}
19
20
21
static void __attribute__ ((naked, used, section(".fini1")))
22
avrtest_fini1 (void)
23
{
24
  /* sycall 30 */
25
  avrtest_exit (avrtest_exit_code);
26
}
27
28
void abort (void)
29
{
30
  /* sycall 31 */
31
  avrtest_abort ();
32
  for (;;);
33
}

von W.S. (Gast)


Lesenswert?

Johann L. schrieb:
> Die Aussage, nach main lande ein C- oder C++-Progamm im "Nirvana",
> entbehrt also jeder Grundlage.

Ach so ist das also.

Also, mein Lieber, deine Aussage ist es, die jeder Grundlage entbehrt.
Es kommt auch nicht auf den Compiler drauf an.
Das was im Falle eines Beendens von main kommt, ist alleinige Sache des 
Startup-Codes.

Ich kenne genügend Startup-Codes, die mit diversen grandiosen IDE's 
mitgeliefert werden, die z.B. bei ARM einfach main mit BX starten. Das 
ist ein Sprung OHNE Rückkehr. Zum Rückkehren bräuchte es nen BLX.

Und einfach mit der CPU durch einen Wald von NOP's oder anderem Gemüse 
zu rasen, in der Hoffnung, beim Überlauf bei 0 anzukommen und dort 
wieder tritt zu fassen, ist sinnlos auf ganz vielen Maschinen. Bei ARM 
landest du in den Vektoren und der Stackpointer ist ohnehin vesaut.

Es ist durchaus eine ernste (aber andere) Frage, ob es klug und 
vorausschauend ist, main ohne Rückkehr anzuspringen, es ist ebenso eine 
Frage, ob es weise ist, bei eventuellem Rücksprung oder bei anderen 
unbeabsichtigten Exceptions den Prozessor in eine Schleife zu legen. 
Immerhin kann er dort ja - falls es mal bloß eine Störung von außen war 
- nix mehr tun, nicht mal neu anfangen.

W.S.

von Bastler (Gast)


Lesenswert?

Klar ist es Sache des Startup-Codes, sich DEFINITIONSGEMÄSS zu 
verhalten.
Und wird waren bei AVR, der kennt kein BX.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

W.S. schrieb:
> Ich kenne genügend Startup-Codes, die mit diversen grandiosen
> IDE's mitgeliefert werden, [...]

Gut.  Dann kennst du viele Implementationen die sich nicht an den 
Standard halten.

Wenn sich eine Implementation — und zu der gehört auch das CRT — nicht 
an den Standart hält, dann ist es müßig, sich darüber zu echauffieren, 
dass die entsprechende Implementation nicht standardkonform ist...

Was von einer nicht-konformen Implementation zu erwarten ist, d.h. wie 
und wo sie nicht konform ist, sollte zwar aus deren Fineprint zu 
entnehmen sein, was aber nichts daran ändert, dass sie nicht konform 
ist.

von Axel S. (a-za-z0-9)


Lesenswert?

Johann L. schrieb:
> Andreas schrieb:
>> Eine kurze Frage: Wenn ich die main Funktion per return 0 verlasse,
>> werden dann auch alle Ausgänge zurückgesetzt (auf low) und alle
>> Interrupts angehalten?
>
> Der C-Standard sagt:
>
>
1
5.1.2.2.3 Program termination
2
> If the return type of the main function is a type compatible with
3
> int, a return from the initial call to the main function is
4
> equivalent to calling the exit function with the value returned
5
> by the main function as its argument; reaching the } that terminates
6
> the main function returns a value of 0. If the return type is not
7
> compatible with int, the termination status returned to the host
8
> environment is unspecified.
>
> Die Aussage, nach main lande ein C- oder C++-Progamm im "Nirvana",
> entbehrt also jeder Grundlage.

IBTD

Der zitierte Abschnitt des Standards sagt lediglich aus, daß ein 
Verlassen von main() äquivalent zum Aufruf von exit() ist. Er sagt aber 
gar nichts darüber aus, wer oder was denn den Returnwert (oder das 
Argument von exit()) entgegennimmt und was er damit anstellt. Das ist 
undefiniert. Aka Nirvana.

Und das ist sicher auch beabsichtigt. Aus Sicht der virtuellen 
C-Maschine gehören argc, argv und der Returnwert von main() zur 
Außenwelt. Sie sind schlicht nicht Bestandteil der Definition der 
Maschine.

In einem hosted environment ist die Sache einigermaßen klar, denn da 
läuft main() nicht von allein los, sondern wird von irgendeiner Instanz 
gestartet, die dann ihrerseits den Returnwert auswerten und irgendwas 
damit anstellen kann.

Aber in einem freestanding environment kommt vor main() lediglich die 
Initialisierung der C Runtime und nach main() die De-Initialisierung. 
Beide gehören streng genommen noch zu main. Aber was nach der 
De-Initialisierung geschieht ist halt unspezifiziert. Und deswegen ist 
es IMNSHO auch ein Bug, wenn man ein bestimmtes Verhalten für diesen 
Fall voraussetzt.

von Bastler (Gast)


Lesenswert?

exit() übergibt die Kontrolle an die "Übergeordnete Instanz". In Unix 
der Parentprozess, auf Reiner Hardware eben diese. Das "Nirvana" ist 
eine Endlosschleife mit gesperrten INT's. Für gezieltes Runterfahren der 
benutzten Geräte gibt es atexit(). Natürlich kann man das alles "nicht 
verstehen", aber dadurch wird nicht automatisch undefiniert, sondern 
nur, auf die Menschheit bezogen, partiell unverstanden.

von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

Johann L. schrieb:
> W.S. schrieb:
>> Ich kenne genügend Startup-Codes, die mit diversen grandiosen
>> IDE's mitgeliefert werden, [...]
>
> Gut.  Dann kennst du viele Implementationen die sich nicht an den
> Standard halten.

Du argumentierst mit dem Standard ohne ihn wirklich zu kennen. Genauer, 
du argumentierst mit den Anforderungen für ein Hosted Environment, 
obwohl es sich beim dieser Diskussion um Free Standing Environments 
handelt.

Der Standard enthält, Kapitel 4, eine Liste von Ausnahmen von sich 
selbst für sogenannte Free Standing Environments. Embedded Anwendungen 
ohne Betriebssystem sind ein Free Standing Environment. Da hat ein 
Return von main() oder ein exit()-Aufruf eben kein definiertes 
Verhalten.

> Wenn sich eine Implementation — und zu der gehört auch das CRT — nicht
> an den Standart hält, dann ist es müßig, sich darüber zu echauffieren,
> dass die entsprechende Implementation nicht standardkonform ist...

Der Einzige der sich her echauffieren bist du - ohne den Standard zu 
kennen.

> Was von einer nicht-konformen Implementation zu erwarten ist, d.h. wie
> und wo sie nicht konform ist,

Undefiniertes Verhalten eines return von main() oder exit() ist 
standardkonform für Free Standing Environments. Also mach mal nicht so 
viel Wind.

: Bearbeitet durch User
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
Noch kein Account? Hier anmelden.
Lade...