Int main () {} (sans «void») est-il valide et portable dans ISO C?

La norme C spécifie deux formes de définition de main pour une implémentation hébergée:

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

et

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

Il peut être défini de manière “équivalente” à celle ci-dessus (par exemple, vous pouvez modifier les noms des parameters, remplacer int par un nom typedef défini comme int ou écrire char *argv[] comme char **argv ).

Il peut également être défini “d’une autre manière définie par l’implémentation” – ce qui signifie que des éléments tels que int main(int argc, char *argv[], char *envp) sont valides si la mise en œuvre les documente.

La clause “d’une autre manière définie par la mise en œuvre” ne figurait pas dans la norme 1989/1990; il a été ajouté par la norme de 1999 (mais la norme antérieure autorisait les extensions, de sorte qu’une implémentation pouvait toujours autoriser d’autres formes).

Ma question est la suivante: étant donné la norme ISO C actuelle (2011), une définition de la forme

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

valide et portable pour toutes les implémentations hébergées?

(Notez que je ne m’adresse pas à void main ni à l’utilisation de int main() sans parenthèses en C ++. Il s’agit simplement de la distinction entre int main(void) et int main() dans ISO C.)

Non.

Selon le libellé normatif de la norme, une définition utilisant des parenthèses vides sans le mot clé void n’est pas l’une des formes à accepter, et le comportement à proprement parler d’un tel programme est indéfini.

Référence: N1570, section 5.1.2.2.1. (La norme ISO C publiée en 2011, qui n’est pas librement disponible, a le même libellé que le projet de norme N1570.)

Le paragraphe 1 dit:

La fonction appelée au démarrage du programme s’appelle main . L’implémentation ne déclare aucun prototype pour cette fonction. Il doit être défini avec un type de retour de int et sans paramètre:

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

ou avec deux parameters (appelés ici argc et argv , bien que tous les noms puissent être utilisés, car ils sont locaux à la fonction dans laquelle ils sont déclarés):

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

ou équivalent; ou d’une autre manière définie par la mise en œuvre.

L’utilisation du mot “shall” en dehors d’une contrainte signifie que tout programme qui la viole a un comportement indéfini. Donc, si, par exemple, j’écris:

 double main(unsigned long ocelots) { return ocelots / 3.14159; } 

un compilateur conforme n’est pas obligé d’imprimer un diagnostic, mais il n’est également pas nécessaire de comstackr le programme ni, s’il le comstack, de se comporter de manière particulière.

Si int main() était équivalent à int main(void) , il serait alors valide et portable pour toute implémentation hébergée conforme. Mais ce n’est pas équivalent.

 int main(void) { } 

fournit à la fois une déclaration (dans ce cas, un prototype) et une définition . La déclaration, en utilisant le mot clé void , spécifie que la fonction n’a pas de paramètre. La définition spécifie la même chose.

Si j’écris à la place:

 int main() { } 

alors j’utilise une déclaration et une définition de style ancien . (De telles déclarations et définitions sont obsolètes , mais elles font toujours partie de la définition du langage et tous les compilateurs conformes doivent toujours les prendre en charge.)

En tant que déclaration, elle ne spécifie pas le nombre ni le type d’arguments attendus par la fonction. En tant que définition, il ne définit aucun paramètre, mais les compilateurs ne doivent pas utiliser ces informations pour diagnostiquer les appels incorrects.

Le DR # 317 inclut la décision de 2006 du comité de norme C selon laquelle une définition avec () ne fournit pas un prototype équivalent à un prototype avec (void) (merci à hvd pour avoir trouvé cette référence).

C permet à main d’être appelée récursivement. Supposons que j’écris:

 int main(void) { if (0) { main(42); } } 

Le prototype visible int main(void) spécifie que main ne prend aucun argument. Un appel qui tente de passer un ou plusieurs arguments viole une contrainte, nécessitant un diagnostic au moment de la compilation.

Ou supposons que j’écris:

 int main() { if (0) { main(42); } } 

Si l’appel main(42) était exécuté, il aurait un comportement indéfini – mais il n’enfreint pas une contrainte et aucun diagnostic n’est requirejs. Comme il est protégé par if (0) , l’appel ne se produit jamais et le comportement indéfini ne se produit jamais. Si nous supposons que int main() est valide, alors ce programme doit être accepté par tout compilateur conforme. Mais à cause de cela, il démontre que int main() n’est pas équivalent à int main(void) , et n’est donc pas couvert par 5.1.2.2.1.

Conclusion: Suivant le libellé de la norme, une implémentation est autorisée à documenter que int main() { } est autorisé. S’il ne le documente pas, il est toujours autorisé à l’accepter sans se plaindre. Mais un compilateur conforme peut également rejeter int main() { } , car il ne s’agit pas d’une des formes autorisées par la norme et son comportement n’est donc pas défini.

Mais il rest une question ouverte: était-ce l’intention des auteurs de la norme?

Avant la publication de la norme ANSI C de 1989, le mot clé void n’existait pas. Les programmes pré-ANSI (K & R) C définiraient main soit

 main() 

ou comme

 int main() 

L’un des principaux objectives de la norme ANSI était d’append de nouvelles fonctionnalités (y compris des prototypes) sans rompre le code pré-ANSI existant. En déclarant que int main() n’est plus valide, cet objective aurait été violé.

Je soupçonne que les auteurs de la norme C n’avaient pas l’ intention de rendre int main() invalide. Mais la norme telle qu’elle est écrite ne reflète pas cette intention; il permet au moins à un compilateur C conforme de rejeter int main() .

Pratiquement , vous pouvez certainement vous en tirer. Tous les compilateurs C que j’ai essayés accepteront

 int main() { return 0; } 

sans se plaindre, avec un comportement équivalent à

 int main(void) { return 0; } 

Mais pour diverses raisons:

  • Suivre à la fois la lettre et l’intention de la norme;
  • Éviter d’utiliser une fonctionnalité obsolète (une future norme pourrait supprimer les définitions de fonction de style ancien);
  • Maintenir de bonnes habitudes de codage (la différence entre () et (void) est importante pour les fonctions autres que la fonction main qui sont réellement appelées par d’autres fonctions

Je recommande de toujours écrire int main(void) plutôt int main() . Il énonce l’intention plus clairement, et vous pouvez être sûr à 100% que votre compilateur l’acceptera, au lieu de 99,9%.

Une indication forte que int main() est censée être valide, que la norme soit ou non correctement indiquée ou non, est le fait que int main() est parfois utilisé dans la norme sans que personne ne soulève d’objection. Bien que les exemples ne soient pas normatifs, ils indiquent une intention.

6.5.3.4 Taille des opérateurs et _Alignof

8 EXEMPLE 3 Dans cet exemple, la taille d’un tableau de longueur variable est calculée et renvoyée par une fonction:

 #include  size_t fsize3(int n) { char b[n+3]; // variable length array return sizeof b; // execution time sizeof } int main() { size_t size; size = fsize3(10); // fsize3 returns 13 return 0; } 

6.7.6.3 Déclarateurs de fonctions (y compris les prototypes)

20 EXEMPLE 4 Le prototype suivant a un paramètre modifié de manière variable.

 void addscalar(int n, int m, double a[n][n*m+300], double x); int main() { double b[4][308]; addscalar(4, 2, b, 2.17); return 0; } void addscalar(int n, int m, double a[n][n*m+300], double x) { for (int i = 0; i < n; i++) for (int j = 0, k = n*m+300; j < k; j++) // a is a pointer to a VLA with n*m+300 elements a[i][j] += x; } 

En ce qui concerne le texte normatif actuel de la norme, je pense que trop de choses sont lues en "équivalentes". Il devrait être assez clair que

 int main (int argc, char *argv[]) { (void) argc; (void) argv; return 0; } 

est valide, et que

 int main (int x, char *y[]) { (void) argc; (void) argv; return 0; } 

est invalide. Néanmoins, la norme indique explicitement dans le texte normatif que tous les noms peuvent être utilisés, ce qui signifie int main (int argc, char *argv[]) et int main (int x, char *y[]) comptent comme équivalents aux fins de 5.1.2.2.1. Le sens anglais ssortingct du mot "équivalent" n’est pas comment il est censé être lu.

Une interprétation un peu plus souple du mot est ce que Keith Thompson suggère dans sa réponse.

Une interprétation également plus souple du mot, tout aussi valable, autorise int main() : int main(void) et int main() définissent main comme une fonction renvoyant int et ne prenant aucun paramètre.

Ni le standard ni aucun DR officiel ne répondent actuellement à la question de savoir quelle interprétation est voulue, donc la question est sans réponse, mais les exemples suggèrent fortement cette dernière interprétation.

Oui.

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

est équivalent à

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

N1570 5.1.2.2.1 / 1

La fonction appelée au démarrage du programme s’appelle main. L’implémentation ne déclare aucun prototype pour cette fonction. Il doit être défini avec un type de retour de int et sans paramètre :

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

ou avec deux parameters (appelés ici argc et argv, bien que tous les noms puissent être utilisés, car ils sont locaux à la fonction dans laquelle ils sont déclarés):

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

ou équivalent; ou d’une autre manière définie par la mise en œuvre.

6.7.6.3/14

Une liste d’identifiants ne déclare que les identifiants des parameters de la fonction. Une liste vide dans un déclarateur de fonction qui fait partie d’une définition de cette fonction spécifie que la fonction n’a pas de paramètre. La liste vide dans un déclarateur de fonction qui ne fait pas partie d’une définition de cette fonction spécifie qu’aucune information sur le nombre ou les types de parameters n’est fournie.

(c’est moi qui souligne)

Comme il est clairement indiqué dans la norme, la définition int main() { /* ... */ } précise que la fonction main n’a pas de paramètre. Et il est clair pour nous tous que cette définition de fonction spécifie que le type de retour de la fonction main est int . Et, comme 5.1.2.2.1 n’exige pas que la déclaration de main ait un prototype, on peut affirmer sans risque que la définition de int main() { /* ... */ } satisfait à toutes les exigences imposées par la norme ( It [the main funtion] shall be defined with a return type of int and with no parameters, or [some other forms] . ).

Néanmoins, vous ne devriez jamais utiliser int main() {} dans votre code, car “l’utilisation de déclarateurs de fonction avec des parenthèses vides (et non des déclarateurs de type de paramètre au format prototype) est une fonctionnalité obsolète”. (6.11.6), et comme cette forme de définition n’inclut pas de déclarateur de prototype de fonction, le compilateur ne vérifiera pas si le nombre et les types d’arguments sont corrects.

N1570 6.5.2.2/8

Aucune autre conversion n’est effectuée implicitement. en particulier, le nombre et les types d’arguments ne sont pas comparés à ceux des parameters d’une définition de fonction n’incluant pas de déclarant de prototype de fonction .

(c’est moi qui souligne)