Comment un programme avec une variable globale appelé main au lieu d’une fonction principale peut-il fonctionner?

Envisagez le programme suivant:

#include  int main = ( std::cout << "C++ is excellent!\n", 195 ); 

En utilisant g ++ 4.8.1 (mingw64) sous Windows 7, le programme comstack et fonctionne correctement, en imprimant:

C ++ est excellent!

à la console. main semble être une variable globale plutôt qu’une fonction; Comment ce programme peut-il être exécuté 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’option -pedantic-errors , mais le programme est toujours compilé et exécuté.

Avant d’entrer dans le vif du sujet au sujet de ce qui se passe, il est important de souligner que le programme est mal formé selon le rapport de défauts 1886: Lien linguistique pour main () :

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

Les versions les plus récentes de clang et gcc en font une erreur et le programme ne comstackra pas ( voir l’exemple live de gcc ):

 error: cannot declare '::main' to be a global variable int main = ( std::cout << "C++ is excellent!\n", 195 ); ^ 

Alors, pourquoi n'y avait-il pas de diagnostic dans les anciennes versions de gcc et de clang? Ce rapport de défaut n’a même pas été proposé jusqu’à la fin de 2014 et ce cas n’a donc été 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 devoir du projet de norme C ++ de la section 3.6.1 [basic.start.main] :

Un programme doit contenir une fonction globale appelée main, qui est le début désigné du programme. [...]

Le comportement non défini est imprévisible et ne nécessite pas de diagnostic. L'incohérence que nous voyons avec la reproduction du comportement est un comportement typique non défini.

Alors, que fait le code et pourquoi, dans certains cas, il produit des résultats? Voyons ce que nous avons:

 declarator | initializer---------------------------------- | | | vvv int main = ( std::cout << "C++ is excellent!\n", 195 ); ^ ^ ^ | | | | | comma operator | primary expression global variable of type int 

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 définie si l'initialisation aura lieu avant qu'une tentative d'appel à main soit faite, mais il semble que gcc le fasse avant d'appeler main .

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 de std::cout . Le résultat de l'opérateur de virgule est l'opérande droit qui, dans ce cas, est la valeur 195 affectée à la variable main .

Nous pouvons voir que sergej souligne que l'assemblage généré montre que cout est appelé lors de l'initialisation statique. Bien que le point le plus intéressant pour la discussion soit la session live godbolt serait la suivante:

 main: .zero 4 

et le suivant:

 movl $195, main(%rip) 

Le scénario probable est que le programme saute au symbole main attendant que le code valide soit présent et dans certains cas, il se produira une faute . Donc, si tel est le cas, on pourrait s'attendre à ce que le stockage d'un code machine valide dans la variable main conduise à un programme viable , en supposant que nous sums situés dans un segment permettant l'exécution de code. Nous pouvons voir que cette entrée de 1984 de l’IOCCC fait exactement cela .

Il semblerait que gcc puisse faire cela en utilisant C ( voir en direct ):

 const int main = 195 ; 

Elle seg-faut si la variable main n'est pas constante car elle ne se trouve pas dans un emplacement exécutable, Hat Tip à ce commentaire ici qui m'a donné cette idée.

Voir aussi FUZxxl répondre ici à une version C spécifique de cette question.

De 3.6.1 / 1:

Un programme doit contenir une fonction globale appelée main, qui est le début désigné du programme. C’est la mise en œuvre définie si un programme dans un environnement autonome est nécessaire pour définir une fonction principale.

À partir de cela, il semble que g ++ autorise un programme (probablement sous la forme d’une clause “autonome”) sans fonction principale.

Puis à partir de 3.6.1 / 3:

La fonction main ne doit pas être utilisée (3.2) dans un programme. Le lien (3.5) de main est défini par l’implémentation. Un programme qui déclare principal être en ligne ou statique est illisible. Le nom principal n’est pas autrement réservé.

Donc, ici, nous apprenons qu’il est parfaitement correct 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 de virgule pour exécuter cout à l’initialisation statique puis fournit une valeur intégrale réelle pour effectuer l’initialisation.

gcc 4.8.1 génère l’assemblage x86 suivant:

 .LC0: .ssortingng "C++ is excellent!\n" subq $8, %rsp #, movl std::__ioinit, %edi #, call std::ios_base::Init::Init() # movl $__dso_handle, %edx #, movl std::__ioinit, %esi #, movl std::ios_base::Init::~Init(), %edi #, call __cxa_atexit # movl $.LC0, %esi #, movl std::cout, %edi #, call std::basic_ostream >& std::operator<<  >(std::basic_ostream >&, char const*) # movl $195, main(%rip) #, main addq $8, %rsp #, ret main: .zero 4 

Notez que cout est appelé lors de l’initialisation, pas dans la fonction main !

.zero 4 déclare 4 octets (0-initialisés) commençant à l’emplacement main , où main est le nom de la variable [!] .

Le symbole main est interprété comme le début du programme. Le comportement dépend de la plateforme.

C’est un programme mal formé. Il se bloque sur mon environnement de test, cygwin64 / g ++ 4.9.3.

De la norme:

3.6.1 Fonction principale [basic.start.main]

1 Un programme doit contenir une fonction globale appelée main, qui est le début désigné du programme.

La raison pour laquelle je pense que cela fonctionne est que le compilateur ne sait pas qu’il comstack la fonction main() sorte qu’il comstack un entier global avec des effets secondaires d’affectation.

Le format d’object dans lequel cette unité de traduction est compilée n’est pas capable de différencier un symbole de fonction d’ un symbole de variable .

Le lieur se lie donc volontiers au symbole principal (variable) et le traite comme un appel de fonction. Mais pas avant que le système d’exécution n’ait exécuté le code d’initialisation des variables globales.

Lorsque j’ai exécuté l’échantillon, il a été imprimé, mais cela a provoqué un défaut de segmentation . Je suppose que le système d’exécution a tenté d’exécuter une variable int comme s’il s’agissait d’une fonction .

J’ai essayé ceci sur un OS Win7 64 bits utilisant VS2013 et il comstack correctement mais quand j’essaie de construire l’application, j’obtiens ce message depuis la fenêtre de sortie.

 1>------ Build started: Project: tempTest, Configuration: Debug Win32 ------ 1>LINK : fatal error LNK1561: entry point must be defined ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ========== 

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 un message et lui atsortingbuer 195. Comme dit par quelqu’un ci-dessous, il est vrai que cela ne réconforte pas avec C ++. 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 et son object définis par l’utilisateur à partir desquels le programme commence à s’exécuter sont spécifiquement le module principal, pas main (). Main encore, main () est appelée par la fonction de démarrage qui est exécutée intentionnellement par loader. Ensuite, toutes vos variables sont initialisées, et lors de l’initialisation, elles sortent ainsi. C’est tout. Programme sans main () est ok, mais pas standard.