main() ohne Parameter?

Das Schreiben der Funktion int main() - also ohne Parameter - ist erlaubt und vollkommen OK.

Doch wie kann das sein? C kennt kein Function-Overloading und wir wissen doch, dass main eigentlich so aussieht:
int main(int argc, char** argv);


(Ja, mir ist bekannt, dass int main(int argc, char* argv[]); schöner aussieht, aber die Zeiger - auf - Zeiger - Version entspricht mehr der Abbildung auf die CPU.)

Da haben wir einen lustigen Seiteneffekt der Sprache C entdeckt. Denn wir können eine Funktion mit mehr Parametern aufrufen, als in ihrer Deklaration angegeben ist.

Folgende beiden Beispiele sollten ohne Fehler kompilieren und ausführbar sein

 1/* test1.c */
 2int test();
 3
 4int test(int a, int b)
 5{
 6  return a + b;
 7}
 8
 9int main()
10{
11  return test(42, 24);
12}

 1/* test2.c */
 2int test();
 3
 4int test()
 5{
 6  return 0;
 7}
 8
 9int main()
10{
11  return test(42, 24);
12}

Dazu braucht es also kein kompliziertes Name-mangling wie in C++, sondern eine Aufrufkonvention, die es erlaubt dieses Feature korrekt in die Maschinensprache zu übersetzen. Und das stellt C (auf allen mir bekannten Plattformen) sicher.

main ist auf den meisten Systemen ja nicht wirklich der originale Startpunkt eines Programms (auch wenn das gerne behauptet wird). Es ist nur die für den Programmierer sichtbare Start-Funktion. Jede Plattform hat ihre native Startprozedur, die die Umgebung initialisiert und dann erst main() aufruft.

 1extern "C" int main();
 2
 3void real_native_entry_point()
 4{
 5  int argc;
 6  char** argv;
 7  int return_value;
 8  
 9  ... /* load values from native platform interface */
10  
11  return_value = main(argc, argv);
12  
13  ... /* native clean-up of process and exit code handling */
14}

Das ist vor allem bei C++ lebenswichtig. Denn wo werden denn sonst die Konstruktoren von globalen Objekten aufgerufen?
Das passiert ebenfalls vor main und deren Zerstörung setzt entsprechend ein, wenn main zurückkehrt, oder per C-API [exit()](http://www.cplusplus.com/reference/cstdlib/exit/) aufgerufen wird.

Und ganz nebenbei erklärt sich jetzt auch, warum die cdecl Aufrufkonvention auf X86 die Parameter in umgekehrter Reihenfolge auf den Stack PUSHt, denn der Stack wächst im Adressraum von oben nach unten und der zuletzt gePUSHte Parameter legt den Beginn des Stack-Frames einer Funktion fest.

test(1, 2, 3, 4, 5); sieht in Assembler dann in etwa so aus:

1PUSH 5
2PUSH 4
3PUSH 3
4PUSH 2
5PUSH 1
6CALL test
7ADD ESP, 20   ; 5 * 4 bytes on 32 bit systems

Heute klingt das für mich alles logisch … doch als ich noch mit PASCAL arbeitete, wo Parameter in genau der Reihenfolge auf den Stack wanderten, wie es im Quelltext beschrieben war, da war C für mich unnötig kompliziert und ‘seltsam’ aufgebaut.

Und das war auch leider mit ein Grund, warum ich mich von C lange fern gehalten hatte.

… hätte mir das doch jemand früher erklärt …

📧 📋 🐘 | 🔔
 

Meine Dokus über:
 
Weitere externe Links zu:
Alle extern verlinkten Webseiten stehen nicht in Zusammenhang mit opengate.at.
Für deren Inhalt wird keine Haftung übernommen.



Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!