Est 'int main;' un programme C / C ++ valide?

113

Je demande parce que mon compilateur semble le penser, même si ce n'est pas le cas.

echo 'int main;' | cc -x c - -Wall
echo 'int main;' | c++ -x c++ - -Wall

Clang n'émet aucun avertissement ou erreur avec ceci, et gcc émet seulement l'avertissement doux:, 'main' is usually a function [-Wmain]mais seulement lorsqu'il est compilé en C. Spécifier a -std=ne semble pas avoir d'importance.

Sinon, il compile et lie très bien. Mais à l'exécution, il se termine immédiatement avec SIGBUS(pour moi).

Lire les (excellentes) réponses à Que devrait retourner main () en C et C ++? et un rapide grep à travers les spécifications du langage, il me semblerait certainement qu'une fonction principale est nécessaire. Mais le verbiage de gcc -Wmain(«main» est généralement une fonction) (et le manque d'erreurs ici) semble suggérer le contraire.

Mais pourquoi? Y a-t-il une utilisation étrange ou «historique» pour cela? Quelqu'un sait ce qui donne?

Mon point, je suppose, est que je pense vraiment que cela devrait être une erreur dans un environnement hébergé, hein?

Geoff Nixon
la source
6
Pour faire de gcc un compilateur (principalement) conforme au standard, vous avez besoingcc -std=c99 -pedantic ...
pmg
3
@pmg C'est le même avertissement, avec ou sans -pedanticou aucun -std. Mon système c99compile également cela sans avertissement ni erreur ...
Geoff Nixon
3
Malheureusement, si vous êtes "assez intelligent", vous pouvez créer des choses qui sont acceptables par le compilateur mais qui n'ont pas de sens. Dans ce cas, vous liez la bibliothèque d'exécution C pour appeler une variable appelée main, qui ne fonctionnera probablement pas. Si vous initialisez main avec la «bonne» valeur, il peut en fait retourner ...
Mats Petersson
7
Et même si c'est valide, c'est une chose horrible à faire (code illisible). BTW, cela pourrait être différent dans les implémentations hébergées et dans les implémentations autonomes (qui ne connaissent pas main)
Basile Starynkevitch
1
Pour des moments plus amusants, essayezmain=195;
imallett

Réponses:

97

Puisque la question porte deux balises en C et C ++, le raisonnement pour C ++ et C serait différent:

  • C ++ utilise la gestion des noms pour aider l'éditeur de liens à distinguer les symboles textuellement identiques de différents types, par exemple une variable globale xyzet une fonction globale autonome xyz(int). Cependant, le nom mainn'est jamais mutilé.
  • C n'utilise pas de découpage, il est donc possible pour un programme de confondre l'éditeur de liens en fournissant un symbole d'un type à la place d'un symbole différent, et de réussir la liaison du programme.

C'est ce qui se passe ici: l'éditeur de liens s'attend à trouver un symbole main, et il le fait. Il «connecte» ce symbole comme s'il s'agissait d'une fonction, car il ne sait pas mieux. La partie de la bibliothèque d'exécution qui passe le contrôle à l' mainéditeur de liens demande main, donc l'éditeur de liens lui donne un symbole main, laissant la phase de liaison se terminer. Bien sûr, cela échoue à l'exécution, car ce mainn'est pas une fonction.

Voici une autre illustration du même problème:

fichier xc:

#include <stdio.h>
int foo(); // <<== main() expects this
int main(){
    printf("%p\n", (void*)&foo);
    return 0;
}

fichier yc:

int foo; // <<== external definition supplies a symbol of a wrong kind

compilation:

gcc x.c y.c

Cela compile, et il s'exécuterait probablement, mais ce comportement n'est pas défini, car le type du symbole promis au compilateur est différent du symbole réel fourni à l'éditeur de liens.

En ce qui concerne l'avertissement, je pense que c'est raisonnable: C vous permet de créer des bibliothèques qui n'ont pas de mainfonction, donc le compilateur libère le nom mainpour d'autres utilisations si vous devez définir une variable mainpour une raison inconnue.

dasblinkenlight
la source
3
Cependant, le compilateur C ++ traite la fonction principale différemment. Son nom n'est pas mutilé même sans "C" externe. Je suppose que c'est parce que sinon il aurait besoin d'émettre son propre externe "C" principal, pour assurer la liaison.
UldisK
@UldisK Oui, je l'ai remarqué moi-même, et je l'ai trouvé assez intéressant. C'est logique, mais je n'y avais jamais pensé.
Geoff Nixon
2
En fait, les résultats pour C ++ et C ne sont pas différents, comme indiqué ici - mainn'est pas soumis à la modification des noms (semble-t-il) en C ++, qu'il s'agisse ou non d'une fonction.
Geoff Nixon
4
@nm Je pense que votre interprétation de la question est trop étroite: en plus de poser la question dans le titre du message, OP cherche clairement à expliquer pourquoi son programme compilé en premier lieu ("mon compilateur semble le penser, même si je ne le fais pas ") ainsi qu'une suggestion expliquant pourquoi il pourrait être utile de définir maincomme autre chose qu'une fonction. La réponse offre une explication pour les deux parties.
dasblinkenlight
1
Le fait que le symbole principal ne soit pas sujet à la modification des noms n'a pas d'importance. Il n'y a aucune mention de dénomination dans la norme C ++. Le changement de nom est un problème de mise en œuvre.
David Hammen
30

mainn'est pas un mot réservé , il est juste un identifiant prédéfini (comme cin, endl, npos...), vous pouvez déclarer une variable appelée main, initialiser et imprimer sa valeur.

Bien sûr:

  • l'avertissement est utile car il est assez sujet aux erreurs;
  • vous pouvez avoir un fichier source sans la main()fonction (bibliothèques).

ÉDITER

Quelques références:

  • main n'est pas un mot réservé (C ++ 11):

    La fonction mainne doit pas être utilisée dans un programme. Le lien (3.5) de mainest défini par l'implémentation. Un programme principal qui définit comme étant supprimés ou qui déclare principal d'être inline, staticou constexprest mal formé. Le nom mainn'est pas réservé autrement. [Exemple: les fonctions membres, les classes et les énumérations peuvent être appelées main, tout comme les entités d'autres espaces de noms. - fin d'exemple]

    C ++ 11 - [basic.start.main] 3.6.1.3

    [2.11 / 3] [...] certains identificateurs sont réservés à l'usage des implémentations C ++ et des bibliothèques standard (17.6.4.3.2) et ne doivent pas être utilisés autrement; aucun diagnostic n'est requis.

    [17.6.4.3.2 / 1] Certains ensembles de noms et signatures de fonctions sont toujours réservés à l'implémentation:

    • Chaque nom contenant un double trait de soulignement __ ou commençant par un trait de soulignement suivi d'une lettre majuscule (2.12) est réservé à l'implémentation pour toute utilisation.
    • Chaque nom commençant par un trait de soulignement est réservé à l'implémentation pour être utilisé comme nom dans l'espace de noms global.
  • Mots réservés dans les langages de programmation .

    Les mots réservés peuvent ne pas être redéfinis par le programmeur, mais les prédéfinis peuvent souvent être remplacés dans une certaine mesure. C'est le cas de main: il existe des portées dans lesquelles une déclaration utilisant cet identifiant redéfinit sa signification.

Manlio
la source
- Je suppose que je suis plutôt séduit par le fait que (car il est si sujet aux erreurs), pourquoi c'est un avertissement (pas une erreur), et pourquoi ce n'est qu'un avertissement lorsqu'il est compilé en C - Bien sûr, vous pouvez compiler sans une main()fonction, mais vous ne pouvez pas la lier en tant que programme. Ce qui se passe ici, c'est qu'un programme "valide" est lié sans main(), juste un main.
Geoff Nixon
7
cinet endlne sont pas dans l'espace de noms par défaut - ils sont dans l' stdespace de noms. nposest membre de std::basic_string.
AnotherParker
1
main est réservé en tant que nom global. Aucune des autres choses que vous avez mentionnées, ni mainn'est prédéfinie.
Potatoswatter
1
Voir C ++ 14 §3.6.1 et C11 §5.1.2.2.1 pour les limitations sur ce qui mainest autorisé à être. C ++ dit "Une implémentation ne doit pas prédéfinir la fonction principale" et C dit "L'implémentation ne déclare aucun prototype pour cette fonction."
Potatoswatter
@manlio: veuillez clarifier ce que vous citez. Quant à la plaine C, les citations sont fausses. Donc je suppose que c'est l'une des normes c ++, n'est-ce pas?
dhein
19

Un programme int main;C / C ++ valide est-il?

Ce qu'est un programme C / C ++ n'est pas tout à fait clair.

Un int main;programme C valide est-il?

Oui. Une implémentation autonome est autorisée à accepter un tel programme. mainn'a pas besoin d'avoir une signification particulière dans un environnement autonome.

Il n'est pas valide dans un environnement hébergé.

Un int main;programme C ++ est -il valide?

Idem.

Pourquoi ça plante?

Le programme n'a pas à avoir de sens dans votre environnement. Dans un environnement autonome, le démarrage et l'arrêt du programme, ainsi que la signification de main, sont définis par l'implémentation.

Pourquoi le compilateur me prévient-il?

Le compilateur peut vous avertir de tout ce qui lui plaît, tant qu'il ne rejette pas les programmes conformes. D'un autre côté, l'avertissement est tout ce qui est nécessaire pour diagnostiquer un programme non conforme. Étant donné que cette unité de traduction ne peut pas faire partie d'un programme hébergé valide, un message de diagnostic est justifié.

Est-ce gccun environnement autonome ou s'agit-il d'un environnement hébergé?

Oui.

gccdocumente l' -ffreestandingindicateur de compilation. Ajoutez-le et l'avertissement disparaît. Vous voudrez peut-être l'utiliser lors de la construction, par exemple, de noyaux ou de micrologiciels.

g++ne documente pas un tel drapeau. Le fournir semble n'avoir aucun effet sur ce programme. Il est probablement prudent de supposer que l'environnement fourni par g ++ est hébergé. L'absence de diagnostic dans ce cas est un bug.

n. «pronoms» m.
la source
17

C'est un avertissement car il n'est pas techniquement interdit. Le code de démarrage utilisera l'emplacement du symbole "main" et y sautera avec les trois arguments standard (argc, argv et envp). Il ne le fait pas, et au moment du lien ne peut pas vérifier qu'il s'agit bien d'une fonction, ni même qu'il a ces arguments. C'est aussi pourquoi int main (int argc, char ** argv) fonctionne - le compilateur ne connaît pas l'argument envp et il se trouve juste qu'il n'est pas utilisé, et c'est le nettoyage de l'appelant.

Pour plaisanter, vous pourriez faire quelque chose comme

int main = 0xCBCBCBCB;

sur une machine x86 et, en ignorant les avertissements et autres choses similaires, il ne se contentera pas de compiler mais fonctionnera aussi.

Quelqu'un a utilisé une technique similaire à celle-ci pour écrire un exécutable (en quelque sorte) qui s'exécute directement sur plusieurs architectures - http://phrack.org/issues/57/17.html#article . Il a également été utilisé pour gagner l'IOCCC - http://www.ioccc.org/1984/mullender/mullender.c .

Dascandy
la source
1
"C'est un avertissement car il n'est pas techniquement interdit" - il est invalide en C ++.
Acclamations et hth. - Alf
3
"les trois arguments standards (argc, argv et envp)" - vous parlez peut-être ici du standard Posix.
Acclamations et hth. - Alf
Sur mon système (Ubuntu 14 / x64), la ligne suivante fonctionne avec gcc:int main __attribute__ ((section (".text")))= 0xC3C3C3C3;
csharpfolk
@ Cheersandhth.-Alf Les deux premiers sont standard, le troisième est POSIX.
dascandy
9

Est-ce un programme valide?

Non.

Ce n'est pas un programme car il n'a pas de parties exécutables.

Est-il valide de compiler?

Oui.

Peut-il être utilisé avec un programme valide?

Oui.

Tout le code compilé ne doit pas nécessairement être exécutable pour être valide. Des exemples sont des bibliothèques statiques et dynamiques.

Vous avez effectivement construit un fichier objet. Ce n'est pas un exécutable valide, mais un autre programme peut créer un lien vers l'objet maindans le fichier résultant en le chargeant au moment de l'exécution.

Cela devrait-il être une erreur?

Traditionnellement, C ++ permet à l'utilisateur de faire des choses qui peuvent sembler ne pas avoir d'utilisation valable mais qui correspondent à la syntaxe du langage.

Je veux dire que bien sûr, cela pourrait être reclassé comme une erreur, mais pourquoi? À quoi cela servirait-il que l'avertissement ne le fasse pas?

Tant qu'il y a une possibilité théorique que cette fonctionnalité soit utilisée dans le code réel, il est très peu probable que le fait d'avoir un objet non fonctionnel appelé mainentraînerait une erreur selon le langage.

Michael Gazonda
la source
Il crée un symbole visible de l'extérieur nommé main. Comment un programme valide, qui doit avoir une fonction visible de l'extérieur nommée main, peut-il y être lié?
Keith Thompson
@KeithThompson Charge lors de l'exécution. Clarifiera.
Michael Gazonda
C'est possible car il ne peut pas faire la différence entre les types de symboles. La liaison fonctionne très bien - l'exécution (sauf dans le cas soigneusement conçu) ne fonctionne pas.
Chris Stratton
1
@ChrisStratton: Je pense que l'argument de Keith est que la liaison échoue parce que le symbole est défini de manière multiple ... parce que le "programme valide" ne serait pas un programme valide à moins qu'il ne définisse une mainfonction.
Ben Voigt
@BenVoigt Mais s'il apparaît dans une bibliothèque, alors la liaison n'échouera pas (et ne pourra probablement pas), car au moment de la liaison du programme, la int main;définition ne sera pas visible.
6

Je voudrais ajouter aux réponses déjà données en citant les normes linguistiques actuelles.

Est 'int main;' un programme C valide?

Réponse courte (mon avis): uniquement si votre implémentation utilise un "environnement d'exécution autonome".

Toutes les citations suivantes de C11

5. Environnement

Une implémentation traduit des fichiers source C et exécute des programmes C dans deux environnements de système de traitement de données, qui seront appelés environnement de traduction et environnement d'exécution [...]

5.1.2 Environnements d'exécution

Deux environnements d'exécution sont définis: autonome et hébergé. Dans les deux cas, le démarrage du programme se produit lorsqu'une fonction C désignée est appelée par l'environnement d'exécution.

5.1.2.1 Environnement autonome

Dans un environnement autonome (dans lequel l'exécution du programme C peut avoir lieu sans aucun avantage d'un système d'exploitation), le nom et le type de la fonction appelée au démarrage du programme sont définis par l'implémentation.

5.1.2.2 Environnement hébergé

Un environnement hébergé n'a pas besoin d'être fourni, mais doit être conforme aux spécifications suivantes, le cas échéant.

5.1.2.2.1 Démarrage du programme

La fonction appelée au démarrage du programme est nommée main . [...] Il doit être défini avec un type de retour int et sans paramètres [...] ou avec deux paramètres [...] ou équivalent ou d'une autre manière définie par l'implémentation.

À partir de ceux-ci, on observe ce qui suit:

  • Un programme C11 peut avoir un environnement d'exécution autonome ou hébergé et être valide.
  • S'il en a une autonome, il n'est pas nécessaire qu'il existe une fonction principale.
  • Sinon, il doit y en avoir un avec une valeur de retour de type int .

Dans un environnement d'exécution autonome, je dirais qu'il s'agit d'un programme valide qui ne permet pas le démarrage, car il n'y a pas de fonction présente pour cela comme requis dans 5.1.2. Dans un environnement d'exécution hébergé, alors que votre code introduit un objet nommé main , il ne peut pas fournir de valeur de retour, je dirais donc que ce n'est pas un programme valide dans ce sens, bien que l'on puisse également argumenter comme avant que si le programme ne l'est pas destiné à être exécuté (on peut vouloir fournir des données uniquement par exemple), alors cela ne permet tout simplement pas de faire cela.

Est 'int main;' un programme C ++ valide?

Réponse courte (mon avis): uniquement si votre implémentation utilise un "environnement d'exécution autonome".

Citation de C ++ 14

3.6.1 Fonction principale

Un programme doit contenir une fonction globale appelée main, qui est le début désigné du programme. Il est défini par l'implémentation si un programme dans un environnement autonome est nécessaire pour définir une fonction principale. [...] Il doit avoir un type de retour de type int, mais sinon, son type est défini par l'implémentation. [...] Le nom main n'est pas réservé autrement.

Ici, contrairement à la norme C11, moins de restrictions s'appliquent à l'environnement d'exécution autonome, car aucune fonction de démarrage n'est mentionnée du tout, tandis que pour un environnement d'exécution hébergé, le cas est à peu près le même que pour C11.

Encore une fois, je dirais que pour le cas hébergé, votre code n'est pas un programme C ++ 14 valide, mais je suis sûr que c'est pour le cas autonome.

Étant donné que ma réponse ne prend en compte que l' environnement d' exécution , je pense que la réponse de dasblinkenlicht entre en jeu, car la modification des noms se produisant dans l' environnement de traduction se produit à l'avance. Ici, je ne suis pas si sûr que les citations ci-dessus soient observées de manière aussi stricte.

Ingo Schalk-Schupp
la source
4

Mon point, je suppose, est que je pense vraiment que cela devrait être une erreur dans un environnement hébergé, hein?

L'erreur est la vôtre. Vous n'avez pas spécifié de fonction nommée mainqui renvoie un intet avez essayé d'utiliser votre programme dans un environnement hébergé.

Supposons que vous ayez une unité de compilation qui définit une variable globale nommée main . Cela pourrait bien être légal dans un environnement autonome car ce qui constitue un programme est laissé à la mise en œuvre dans des environnements autonomes.

Supposons que vous ayez une autre unité de compilation qui définit une fonction globale nommée mainqui renvoie un intet ne prend aucun argument. C'est exactement ce dont un programme dans un environnement hébergé a besoin.

Tout va bien si vous n'utilisez que la première unité de compilation dans un environnement autonome et n'utilisez que la seconde dans un environnement hébergé. Et si vous utilisez les deux dans un même programme? En C ++, vous avez enfreint la règle de définition unique. C'est un comportement indéfini. En C, vous avez violé la règle qui veut que toutes les références à un seul symbole soient cohérentes; s'ils ne le sont pas, c'est un comportement indéfini. Un comportement indéfini est un "sortez de prison, gratuitement!" carte aux développeurs d'une implémentation. Tout ce qu'une implémentation fait en réponse à un comportement indéfini est conforme à la norme. L'implémentation n'a pas à avertir, encore moins à détecter, un comportement non défini.

Et si vous n'utilisez qu'une seule de ces unités de compilation, mais que vous utilisez la mauvaise (ce que vous avez fait)? En C, la situation est claire. Le fait de ne pas définir la fonction maindans l'un des deux formulaires standard dans un environnement hébergé est un comportement indéfini. Supposons que vous ne définissiez pas maindu tout. Le compilateur / éditeur de liens n'a rien à dire à propos de cette erreur. Qu'ils se plaignent est une gentillesse en leur nom. Que le programme C compilé et lié sans erreur est de votre faute, pas de celle du compilateur.

C'est un peu moins clair en C ++ car l'échec de la définition de la fonction maindans un environnement hébergé est une erreur plutôt qu'un comportement indéfini (en d'autres termes, il doit être diagnostiqué). Cependant, la règle de définition unique en C ++ signifie que les éditeurs de liens peuvent être plutôt stupides. Le travail de l'éditeur de liens est de résoudre les références externes, et grâce à la règle d'une définition unique, l'éditeur de liens n'a pas besoin de savoir ce que signifient ces symboles. Vous avez fourni un symbole nommé main, l'éditeur de liens s'attend à voir un symbole nommé main, donc tout va bien en ce qui concerne l'éditeur de liens.

David Hammen
la source
4

Pour C jusqu'à présent, c'est le comportement défini par l'implémentation.

Comme le dit l'ISO / CEI9899:

5.1.2.2.1 Démarrage du programme

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

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

ou avec deux paramètres (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 l'implémentation.

Dhein
la source
3

Non, ce n'est pas un programme valide.

Pour C ++, cela a été récemment explicitement rendu mal formé par le rapport de défaut 1886: Lien de langage pour main () qui dit:

Il ne semble pas y avoir de restriction à donner à main () un lien de langage explicite, mais il devrait probablement être soit mal formé, soit conditionnellement soutenu.

et une partie de la résolution comprenait le changement suivant:

Un programme qui déclare une variable main à portée globale ou qui déclare le nom main avec une liaison en langage C (dans n'importe quel espace de noms) est mal formé.

Nous pouvons trouver ce libellé dans le dernier projet de norme C ++ N4527 qui est le projet C ++ 1z.

Les dernières versions de clang et gcc font maintenant une erreur ( voyez-le en direct ):

error: main cannot be declared as global variable
int main;
^

Avant ce rapport de défaut, il s'agissait d'un comportement non défini qui ne nécessite pas de diagnostic. D'un autre côté, un code mal formé nécessite un diagnostic, le compilateur peut en faire un avertissement ou une erreur.

Shafik Yaghmour
la source
Merci pour la mise à jour! C'est formidable de voir que cela est maintenant pris en compte avec les diagnostics du compilateur. Cependant, je dois dire que je trouve les changements dans la norme C ++ perplexes. (Pour le contexte, voir les commentaires ci-dessus concernant la modification des noms de main().) Je comprends la justification de l'interdiction main()d'avoir une spécification de lien explicite, mais je ne comprends pas qu'il soit obligatoire d' main()avoir un lien C ++ . Bien sûr , la norme ne concerne pas directement l' adresse comment gérer la liaison ABI / nom mutiler, mais dans la pratique ( par exemple, avec ABI Itanium) ce serait mutiler main()à _Z4mainv. Qu'est-ce que je rate?
Geoff Nixon du
Je pense que le commentaire de supercat couvre cela. Si l'implémentation fait sa propre chose avant d'appeler le main défini par l'utilisateur, elle pourrait facilement choisir d'appeler un nom mutilé à la place.
Shafik Yaghmour