Considérez le programme suivant:
#include <iostream>
int main = ( std::cout << "C++ is excellent!\n", 195 );
En utilisant g ++ 4.8.1 (mingw64) sur le système d'exploitation Windows 7, le programme se compile et fonctionne correctement, imprimant:
Le C ++ est excellent!
à la console. main
semble être une variable globale plutôt qu'une fonction; comment ce programme peut-il s'exécuter sans la fonction main()
? Ce code est-il conforme à la norme C ++? Le comportement du programme est-il bien défini? J'ai également utilisé l' -pedantic-errors
option mais le programme se compile et s'exécute toujours.
c++
main
language-lawyer
Destructeur
la source
la source
195
s'agit de l'opcode pour l'RET
instruction et que dans la convention d'appel C, l'appelant efface la pile.main()
fonction? En fait, ils ne sont absolument pas liés.)int main = ( std::cout << "C++ is excellent!\n", exit(0),1 );
(et en l'incluant<cstdlib>
), même si le programme reste juridiquement mal formé.Réponses:
Avant d'entrer dans le vif du sujet de la question de ce qui se passe, il est important de souligner que le programme est mal formé selon le rapport de défaut 1886: Lien de langue pour main () :
Les versions les plus récentes de clang et gcc en font une erreur et le programme ne se compilera pas ( voir l'exemple gcc live ):
Alors pourquoi n'y avait-il pas de diagnostic dans les anciennes versions de gcc et clang? Ce rapport de défaut n'avait même pas de résolution proposée jusqu'à la fin de 2014 et ce cas n'était donc que très récemment explicitement mal formé, ce qui nécessite un diagnostic.
Avant cela, il semble que ce soit un comportement indéfini puisque nous violons une exigence de shall du projet de norme C ++ de la section
3.6.1
[basic.start.main] :Un comportement non défini est imprévisible et ne nécessite pas de diagnostic. L'incohérence que nous constatons avec la reproduction du comportement est un comportement non défini typique.
Alors, que fait réellement le code et pourquoi dans certains cas produit-il des résultats? Voyons ce que nous avons:
Nous avons
main
qui est un int déclaré dans l'espace de noms global et qui est en cours d'initialisation, la variable a une durée de stockage statique. C'est l'implémentation qui définit si l'initialisation aura lieu avant qu'une tentative d'appel nemain
soit faite, mais il semble que gcc le fasse avant d'appelermain
.Le code utilise l' opérateur virgule , l'opérande gauche est une expression de valeur ignorée et est utilisé ici uniquement pour l'effet secondaire de l'appel
std::cout
. Le résultat de l'opérateur virgule est l'opérande de droite qui, dans ce cas, est la prvalue195
qui est affectée à la variablemain
.Nous pouvons voir que sergej indique que l'assembly généré montre qu'il
cout
est appelé lors de l'initialisation statique. Bien que le point le plus intéressant pour la discussion voir la session de godbolt en direct serait le suivant:et le suivant:
Le scénario probable est que le programme saute au symbole
main
s'attendant à ce qu'un code valide soit présent et, dans certains cas, seg-fault . Donc, si tel est le cas, nous nous attendons à ce que le stockage de code machine valide dans la variablemain
puisse conduire à un programme exploitable , en supposant que nous soyons situés dans un segment qui permet l'exécution de code. Nous pouvons voir que cette entrée du IOCCC de 1984 fait exactement cela .Il semble que nous pouvons demander à gcc de le faire en C en utilisant ( voir en direct ):
Il seg-faille si la variable
main
n'est pas const vraisemblablement car elle n'est pas située dans un emplacement exécutable, Hat Tip à ce commentaire ici qui m'a donné cette idée.Voir également la réponse FUZxxl ici à une version spécifique C de cette question.
la source
main
n'est pas un identifiant réservé (3.6.1 / 3). Dans ce cas, je pense que la gestion de ce cas par VS2013 (voir la réponse de Francis Cugler) est plus correcte dans sa gestion que gcc & clang.À partir du 3.6.1 / 1:
A partir de là, il semble que g ++ autorise un programme (vraisemblablement comme la clause "autonome") sans fonction principale.
Puis à partir du 3.6.1 / 3:
Donc, ici, nous apprenons qu'il est parfaitement bien d'avoir une variable entière nommée
main
.Enfin, si vous vous demandez pourquoi la sortie est imprimée, l'initialisation de l '
int main
utilise l'opérateur virgule pour s'exécutercout
à l'initialisation statique, puis fournit une valeur intégrale réelle pour effectuer l'initialisation.la source
main
avec quelque chose d'autre:(.text+0x20): undefined reference to
main ''gcc 4.8.1 génère l'assembly x86 suivant:
Notez qu'il
cout
est appelé lors de l'initialisation, pas dans lamain
fonction!.zero 4
déclare 4 octets (initialisés à 0) à partir de l'emplacementmain
, oùmain
est le nom de la variable [!] .Le
main
symbole est interprété comme le début du programme. Le comportement dépend de la plate-forme.la source
195
l'opcode pourret
certaines architectures. Donc, dire zéro instruction peut ne pas être exact.C'est un programme mal formé. Il plante sur mon environnement de test, cygwin64 / g ++ 4.9.3.
De la norme:
la source
La raison pour laquelle je pense que cela fonctionne est que le compilateur ne sait pas qu'il compile la
main()
fonction afin qu'il compile un entier global avec des effets secondaires d'affectation.Le format d'objet dans lequel cette unité de traduction est compilée n'est pas capable de faire la différence entre un symbole de fonction et un symbole variable .
Ainsi, l' éditeur de liens se connecte volontiers au symbole principal (variable) et le traite comme un appel de fonction. Mais pas tant que le système d'exécution n'a pas exécuté le code d'initialisation de la variable globale.
Lorsque j'ai analysé l'échantillon, il s'est imprimé, mais cela a provoqué un défaut de segmentation . Je suppose que c'est à ce moment que le système d'exécution a essayé d'exécuter une variable int comme s'il s'agissait d'une fonction .
la source
J'ai essayé cela sur un système d'exploitation Win7 64 bits utilisant VS2013 et il se compile correctement, mais lorsque j'essaye de créer l'application, je reçois ce message de la fenêtre de sortie.
la source
main()
car il s'agit d'une variable de typeint
Vous faites un travail délicat ici. Comme main (en quelque sorte) pourrait être déclaré entier. Vous avez utilisé l'opérateur de liste pour imprimer le message, puis lui attribuer 195. Comme l'a dit quelqu'un ci-dessous, le fait que cela ne soit pas à l'aise avec C ++ est vrai. Mais comme le compilateur n'a trouvé aucun nom défini par l'utilisateur, main, il ne s'est pas plaint. Rappelez-vous que main n'est pas une fonction définie par le système, sa fonction définie par l'utilisateur et la chose à partir de laquelle le programme commence à s'exécuter est le module principal, pas main (), en particulier. Encore une fois, main () est appelée par la fonction de démarrage qui est exécutée intentionnellement par le chargeur. Ensuite, toutes vos variables sont initialisées, et lors de l'initialisation, la sortie est comme ça. C'est tout. Le programme sans main () est correct, mais pas standard.
la source