Prototype de main() en C

Ah ! Le commentaire classique lu sur le net quand quelqu’un montre son code : « hey ! ton main() n’est pas conforme à la norme ! « . Ouais… Mais il doit avoir quelle tête pour être conforme à la norme ? Cet article propose une petite exploration de la norme C99 à ce sujet.

Le prototype de main()

Le premier stop se fait à la section 5.1.2.2.1 Program startup où est décrit le point d’entrée d’un programme C, à savoir la fonction main(). Elle donne à elle seule presque toute la réponse à la question :

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[]) { /* ... */ }
or equivalent;9)
or in some other implementation-defined manner.

La note de bas de page 9 concerne l’équivalence possible aux deux prototypes données :

9) Thus, int can be replaced by a typedef name defined as int, or the type of argv can be written as char ** argv, and so on.

On se retrouve donc avec 2 prototypes classiques clairement définie par la norme :

int main(void) { /* ... */ }
int main(int argc, char *argv[]) { /* ... */ }

On accepte donc les équivalents à ces deux prototypes :

int main(int arg, char **argv) { /* ... */ }

typedef entier int;
entier main(void) { /* ... */ }
entier main(entier argc, char *argv[]) { /* ... */ }
entier main(int argc, char **argv) { /* ... */ }

…et tous ceux que votre imagination pourra trouver.

Enfin, on trouve n’importe quel autre prototype choisi par l’implémentation. Ils ne seront pas portables, donc pas vraiment « standard », mais ils seront conformes à la norme en rentrant dans la case or in some other implementation-defined manner. On retrouvera par exemple souvent des compilateurs pour micro-contrôleurs avec un prototype sans retour, puisqu’il n’y a personne à qui retourner ce code : void main(void) { /* ... */ }. Mac OS X autorise quant à lui le prototype suivant int main(int argc, char **argv, char **envp, char **apple) { /* ... */ }.

Attention : cela ne veut pas dire que vous pouvez définir main() comme vous voulez. Vous devez respecter les choix d’implémentation faits par votre compilateur. Pour aller plus loin sur ce sujet, vous pouvez lire les question 2.18 et 11.12b de c-faq.

main() sans paramètre

Il est correct d’écrire ceci :

int main() { /* .... */ }

C’est tout à fait équivalent à :

int main(void) { /* .... */ }

C’est possible, comme très bien expliqué par Patrick Gonord dans ce message, grâce au point 14 de la section 6.7.5.3 Function declarators (including prototypes) :

An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.

C’est possible uniquement lors de la définition de la fonction et non lors d’une simple déclaration, pour main() comme pour n’importe quelle fonction. Ainsi, on ne pourra pas écrire de manière équivalente les prototypes void f(); et void f(void);.

Autres trucs intéressants

1) Ça ne sert rien de rappeler le prototype de main() dans son code ou de chercher un fichier d’en-tête le faisant. Il suffit de définir cette fonction. La norme le dit à la section 5.1.2.2.1 Program startup :

The implementation declares no prototype for this function.

2) Atteindre l’accolade fermante de la fonction main() retourne la valeur 0. En effet, un return 0; est implicitement rajouté à la fin de main() si le développeur n’écrit pas de retour explicite. La norme C99 le confirme à la section 5.1.2.2.3 Program termination. On pourra d’ailleurs remarquer que compiler sans return avec gcc sans lui préciser le standard génère un warning (le standard par défaut étant gnu90) et que rajouter -std=c99 le supprime.

3) Il n’est pas possible de ne pas écrire explicitement le type de retour de main(). Si vous avez lu les liens vers c-faq, vous le savez déjà ou vous vous en doutez. Cette discussion de stackoverflow m’a aiguillé vers la point 5 de la section Foreword. Il y est indiqué que les implicit int ont été retirés. Le célèbre livre K&R possède donc des main() qui ne sont pas conformes à la norme C99 mais seulement à la norme ANSI à laquelle le livre est consacré. Vous pouvez lire cette discussion de stackoverflow à ce sujet.

Publicités

2 Réponses

  1. Pour une fois je n’ai pas grand chose à ajouter!

    Quelques précisions cependant:

    Le troisième paramètre (env ou envp) à main contient souvent les variables d’environnement. La doc de la gnu libc dit que c’est « Unix », mais précise ensuite que c’est interdit par POSIX. Il vaut donc mieux éviter cette écriture et utiliser au choix, la variable globale « environ », ou les fonctions getenv et setenv.

    Je voulais aussi ajouter que la fonction main n’a se rôle spécial qu’en C hosted, et pas en C freestanding, ou le point d’entrée n’est pas défini par la norme. Cela dit, rien n’empêche de l’appeler main aussi dans ce cas – sans restriction sur le prototype, les détails étant à régler avec ce qui appelera la fonction en question: vecteur de reset, bootloader, code assembleur d’initialisation, …

    J'aime

    16 décembre 2013 à 9:09

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s