Certaines des fonctionnalités du langage C ont commencé comme des hacks qui ont fonctionné.
Les signatures multiples pour les listes d'arguments principales et de longueur variable font partie de ces fonctionnalités.
Les programmeurs ont remarqué qu'ils peuvent passer des arguments supplémentaires à une fonction, et rien de mauvais ne se produit avec leur compilateur donné.
C'est le cas si les conventions d'appel sont telles que:
- La fonction appelante nettoie les arguments.
- Les arguments les plus à gauche sont plus proches du haut de la pile, ou de la base du cadre de la pile, de sorte que des arguments faux n'invalident pas l'adressage.
Un ensemble de conventions d'appel qui obéit à ces règles est le passage de paramètre basé sur la pile, par lequel l'appelant affiche les arguments, et ils sont poussés de droite à gauche:
;; 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
Dans les compilateurs où ce type de convention d'appel est le cas, rien de spécial ne doit être fait pour prendre en charge les deux types de main
, voire des types supplémentaires. main
peut être une fonction d'aucun argument, auquel cas il est inconscient des éléments qui ont été poussés sur la pile. Si c'est une fonction de deux arguments, alors il trouve argc
et argv
comme les deux éléments de pile supérieurs. S'il s'agit d'une variante à trois arguments spécifique à la plate-forme avec un pointeur d'environnement (une extension commune), cela fonctionnera également: il trouvera ce troisième argument comme troisième élément à partir du haut de la pile.
Ainsi, un appel fixe fonctionne dans tous les cas, permettant à un seul module de démarrage fixe d'être lié au programme. Ce module pourrait être écrit en C, comme une fonction ressemblant à ceci:
/* 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));
}
En d'autres termes, ce module de démarrage appelle simplement un main à trois arguments, toujours. Si main ne prend aucun argument, ou seulement int, char **
, cela fonctionne correctement, ainsi que s'il ne prend aucun argument, en raison des conventions d'appel.
Si vous deviez faire ce genre de chose dans votre programme, ce serait non portable et considéré comme un comportement indéfini par ISO C: déclarer et appeler une fonction d'une manière et la définir d'une autre. Mais l'astuce de démarrage d'un compilateur n'a pas besoin d'être portable; il n'est pas guidé par les règles des programmes portables.
Mais supposons que les conventions d'appel soient telles que cela ne peut pas fonctionner de cette façon. Dans ce cas, le compilateur doit traitermain
spécialement. Lorsqu'il remarque qu'il compile la main
fonction, il peut générer du code compatible avec, par exemple, un appel à trois arguments.
C'est-à-dire que vous écrivez ceci:
int main(void)
{
/* ... */
}
Mais lorsque le compilateur le voit, il effectue essentiellement une transformation de code afin que la fonction qu'il compile ressemble plus à ceci:
int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
/* ... */
}
sauf que les noms __argc_ignore
n'existent pas littéralement. Aucun de ces noms n'est introduit dans votre portée, et il n'y aura aucun avertissement concernant les arguments inutilisés. La transformation de code oblige le compilateur à émettre du code avec la liaison correcte qui sait qu'il doit nettoyer trois arguments.
Une autre stratégie d'implémentation consiste pour le compilateur ou peut-être l'éditeur de liens à générer la __start
fonction (ou quel que soit son nom), ou au moins en sélectionner une parmi plusieurs alternatives précompilées. Des informations peuvent être stockées dans le fichier objet sur lequel des formulaires pris en charge de main
est utilisé. L'éditeur de liens peut consulter ces informations et sélectionner la version correcte du module de démarrage qui contient un appel main
compatible avec la définition du programme. Les implémentations C n'ont généralement qu'un petit nombre de formes prises en charge, main
donc cette approche est faisable.
Les compilateurs pour le langage C99 doivent toujours traiter main
spécialement, dans une certaine mesure, pour prendre en charge le hack que si la fonction se termine sans return
instruction, le comportement est comme si elle return 0
était exécutée. Ceci, encore une fois, peut être traité par une transformation de code. Le compilateur remarque qu'une fonction appelée main
est en cours de compilation. Ensuite, il vérifie si l'extrémité du corps est potentiellement accessible. Si tel est le cas, il insère unreturn 0;
main
méthode dans un seul programme dansC
(ou, vraiment, dans à peu près n'importe quel langage avec une telle construction).