97 votes

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

Envisagez 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 s'exécute correctement, en 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é le -pedantic-errors mais le programme se compile et s'exécute toujours.

1 votes

MSVC++14.0 se plaint de - LNK1561 : le point d'entrée doit être défini

0 votes

Utilisez-vous le strict paramètre du compilateur ? Quels sont les paramètres du compilateur que vous utilisez ?

0 votes

@Fireho : MSVS 2010 montre également une erreur de liaison. Mais pourquoi g++ l'accepte-t-il ?

86voto

Shafik Yaghmour Points 42198

Avant d'entrer dans le vif du sujet, il est important de souligner que le programme est mal formé, conformément à l'article 2 de la loi sur la protection des données. rapport de défaut 1886 : Lien avec le langage pour main() :

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

Dans les versions les plus récentes de clang et de gcc, cela constitue une erreur et le programme ne sera pas compilé ( voir l'exemple de gcc en direct ):

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 clang ? Ce rapport de défaut n'a même pas eu de résolution proposée avant fin 2014 et donc ce cas n'a é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 règle de base de l'UE. est l'exigence 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 constitue 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 constatons en reproduisant le comportement est un comportement indéfini typique.

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

declarator  
|        initializer----------------------------------
|        |                                           |
v        v                                           v
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ée dans l'espace de noms global et en cours d'initialisation, la variable a une durée de stockage statique. Il est défini dans l'implémentation si l'initialisation aura lieu avant une tentative d'appel à la fonction main est faite mais il semble que gcc le fasse avant d'appeler main .

Le code utilise le opérateur de virgule l'opérande de gauche est une expression de valeur rejeté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 valeur pr. 195 qui est assigné à la variable main .

Nous pouvons voir sergej fait remarquer l'assemblage généré montre que cout est appelé pendant l'initialisation statique. Bien que le point le plus intéressant pour la discussion voir la session live de 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 s'attendre à ce que le code valide soit là et dans dans certains cas, il y aura des erreurs de segmentation . Donc, si c'est le cas, nous nous attendons à stocker un code machine valide dans la variable main pourrait conduire à programme viable en supposant que nous nous trouvons dans un segment qui permet l'exécution du code. Nous pouvons voir cette entrée de 1984 de l'IOCCC hace juste que .

Il semble que l'on puisse faire en sorte que gcc le fasse en C en utilisant ( voir en direct ):

const int main = 195 ;

Il se met en défaut si la variable main n'est pas const, probablement parce qu'il n'est pas situé dans un emplacement exécutable. commentez ici ce qui m'a donné cette idée.

Voir aussi Réponse FUZxxl ici à une version spécifique C de cette question.

0 votes

Pourquoi la mise en œuvre ne donne pas d'avertissements aussi. (Lorsque j'utilise -Wall & -Wextra, elle ne donne toujours pas d'avertissement). Pourquoi ? Que pensez-vous de la réponse de @Mark B à cette question ?

0 votes

IMHO, le compilateur ne devrait pas donner un avertissement parce que 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 que celle de gcc et clang.

0 votes

@PravasiMeet J'ai mis à jour ma réponse concernant la raison pour laquelle les versions précédentes de gcc ne donnaient pas de diagnostic.

20voto

Mark B Points 60200

De 3.6.1/1 :

Un programme doit contenir une fonction globale appelée main, qui est le début désigné du programme. L'implémentation est définie si un programme dans un environnement autonome doit définir une fonction main. principale.

D'après ce qui précède, il semble que g++ autorise un programme (vraisemblablement par la clause "freestanding") sans fonction principale.

Ensuite, à partir de 3.6.1/3 :

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

Nous apprenons donc ici qu'il est parfaitement possible d'avoir une variable entière nommée main .

Enfin, si vous vous demandez pourquoi la sortie est imprimée, l'initialisation de la fonction int main utilise l'opérateur virgule pour exécuter cout à static init et ensuite fournir une valeur intégrale réelle pour faire l'initialisation.

7 votes

Il est intéressant de noter que la liaison échoue si vous renommez main à quelque chose d'autre : (.text+0x20): undefined reference to main' ``

1 votes

Ne devez-vous pas spécifier à gcc que votre programme est indépendant ?

10voto

sergej Points 1749

Gcc 4.8.1 génère l'assemblage x86 suivant :

.LC0:
    .string "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<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)   #
    movl    $195, main(%rip)    #, main
    addq    $8, %rsp    #,
    ret
main:
    .zero   4

Notez que cout est appelé lors de l'initialisation, et non dans le main fonction !

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

El main est interprété comme le début du programme. Le comportement dépend de la plate-forme.

1 votes

Note comme Brian souligne 195 est l'opcode pour ret sur certaines architectures. Ainsi, dire zéro instruction n'est pas forcément exact.

0 votes

@ShafikYaghmour Merci pour votre commentaire, vous avez raison. Je me suis embrouillé avec les directives de l'assembleur.

8voto

R Sahu Points 24027

C'est un programme mal ficelé. Il se plante 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 constitue le début désigné du programme.

0 votes

Je pense qu'avant le rapport de défaut que j'ai cité, c'était juste un comportement non défini.

0 votes

@ShafikYaghmour, Est-ce le principe général qui doit être appliqué à tous les endroits où la norme utilise est ?

0 votes

J'ai envie de dire oui mais je n'ai pas vu une bonne description de la différence. De ce que je peux dire de cette discussion En effet, NDR mal formé et comportement indéfini sont probablement synonymes puisque ni l'un ni l'autre ne nécessite un diagnostic. Cela semble impliquer que mal formé et UB sont distincts, mais je n'en suis pas sûr.

7voto

Galik Points 522

La raison pour laquelle je pense que cela fonctionne est que le compilateur ne sait pas qu'il compile le main() pour qu'elle compile un entier global avec des effets secondaires d'affectation.

El format de l'objet que ce unité de traduction est compilé n'est pas capable de faire la différence entre une symbole de fonction et un symbole variable .

Ainsi, le linker se lie heureusement à la (variable) principal et le traite comme un appel de fonction. Mais pas avant que le système d'exécution a exécuté le code d'initialisation de la variable globale.

Lorsque j'ai exécuté l'échantillon, il s'est imprimé, mais il a provoqué une erreur d'impression. défaut de segmentation . Je suppose que c'est à ce moment-là que le système d'exécution a essayé d'exécuter un variable int comme s'il s'agissait d'un fonction .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X