Einige der Funktionen der C-Sprache begannen als Hacks, die gerade funktionierten.
Eine dieser Funktionen sind mehrere Signaturen für Hauptargumentlisten sowie Argumentlisten mit variabler Länge.
Programmierer haben festgestellt, dass sie zusätzliche Argumente an eine Funktion übergeben können und mit ihrem angegebenen Compiler nichts Schlimmes passiert.
Dies ist der Fall, wenn die Aufrufkonventionen so sind, dass:
- Die aufrufende Funktion bereinigt die Argumente.
- Die Argumente ganz links befinden sich näher am oberen Rand des Stapels oder an der Basis des Stapelrahmens, sodass falsche Argumente die Adressierung nicht ungültig machen.
Eine Reihe von Aufrufkonventionen, die diesen Regeln entsprechen, ist die stapelbasierte Parameterübergabe, bei der der Aufrufer die Argumente einfügt und sie von rechts nach links verschoben werden:
;; pseudo-assembly-language
;; main(argc, argv, envp); call
push envp ;; rightmost argument
push argv ;;
push argc ;; leftmost argument ends up on top of stack
call main
pop ;; caller cleans up
pop
pop
In Compilern, in denen diese Art von Aufrufkonvention der Fall ist, muss nichts Besonderes getan werden, um die beiden Arten mainoder sogar zusätzliche Arten zu unterstützen. mainkann eine Funktion ohne Argumente sein. In diesem Fall werden die Elemente, die auf den Stapel geschoben wurden, nicht berücksichtigt. Wenn es eine Funktion von zwei Argumenten ist, findet es argcund argvals die beiden obersten Stapelelemente. Wenn es sich um eine plattformspezifische Variante mit drei Argumenten und einem Umgebungszeiger (eine allgemeine Erweiterung) handelt, funktioniert dies ebenfalls: Das dritte Argument wird als drittes Element oben im Stapel gefunden.
Ein fester Aufruf funktioniert also in allen Fällen, sodass ein einzelnes festes Startmodul mit dem Programm verknüpft werden kann. Dieses Modul könnte in C geschrieben werden, als eine Funktion, die dieser ähnelt:
/* I'm adding envp to show that even a popular platform-specific variant
can be handled. */
extern int main(int argc, char **argv, char **envp);
void __start(void)
{
/* This is the real startup function for the executable.
It performs a bunch of library initialization. */
/* ... */
/* And then: */
exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere));
}
Mit anderen Worten, dieses Startmodul ruft immer nur ein Haupt mit drei Argumenten auf. Wenn main keine oder nur keine Argumente int, char **akzeptiert, funktioniert es aufgrund der aufrufenden Konventionen einwandfrei und wenn keine Argumente akzeptiert werden.
Wenn Sie so etwas in Ihrem Programm tun würden, wäre es nicht portierbar und würde von ISO C als undefiniertes Verhalten angesehen: Deklarieren und Aufrufen einer Funktion auf eine Weise und Definieren in einer anderen. Der Starttrick eines Compilers muss jedoch nicht portabel sein. Es richtet sich nicht nach den Regeln für tragbare Programme.
Angenommen, die Aufrufkonventionen sind so, dass sie nicht so funktionieren können. In diesem Fall muss der Compiler mainspeziell behandeln . Wenn es bemerkt, dass es die mainFunktion kompiliert , kann es Code generieren, der beispielsweise mit einem Aufruf mit drei Argumenten kompatibel ist.
Das heißt, Sie schreiben Folgendes:
int main(void)
{
/* ... */
}
Wenn der Compiler es sieht, führt er im Wesentlichen eine Codetransformation durch, sodass die von ihm kompilierte Funktion folgendermaßen aussieht:
int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
/* ... */
}
außer dass die Namen __argc_ignorenicht buchstäblich existieren. In Ihren Bereich werden keine solchen Namen aufgenommen, und es wird keine Warnung vor nicht verwendeten Argumenten angezeigt. Die Codetransformation bewirkt, dass der Compiler Code mit der richtigen Verknüpfung ausgibt, die weiß, dass drei Argumente bereinigt werden müssen.
Eine andere Implementierungsstrategie besteht darin, dass der Compiler oder Linker die __startFunktion (oder wie auch immer sie genannt wird) benutzerdefiniert generiert oder zumindest eine aus mehreren vorkompilierten Alternativen auswählt. In der Objektdatei können Informationen darüber gespeichert werden, welche der unterstützten Formen mainverwendet werden. Der Linker kann sich diese Informationen ansehen und die richtige Version des Startmoduls auswählen, die einen Aufruf enthält, mainder mit der Programmdefinition kompatibel ist. C-Implementierungen haben normalerweise nur eine geringe Anzahl unterstützter Formen, mainsodass dieser Ansatz möglich ist.
Compiler für die C99-Sprache müssen immer bis mainzu einem gewissen Grad speziell behandeln , um den Hack zu unterstützen, dass returndas Verhalten so return 0ausgeführt wird, als ob sie ausgeführt würden , wenn die Funktion ohne Anweisung beendet wird . Dies kann wiederum durch eine Codetransformation behandelt werden. Der Compiler bemerkt, dass eine aufgerufene Funktion mainkompiliert wird. Dann wird geprüft, ob das Ende des Körpers möglicherweise erreichbar ist. Wenn ja, wird a eingefügtreturn 0;
mainMethode in einem einzelnen Programm inC(oder wirklich in so ziemlich jeder Sprache mit einem solchen Konstrukt) haben.