web-dev-qa-db-fra.com

Comment un programme avec une variable globale appelée main au lieu d'une fonction principale 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 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 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'option -pedantic-errors mais le programme compile et s'exécute toujours.

97
Destructor

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

[...] 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é. [...]

Les versions les plus récentes de clang et gcc en font une erreur et le programme ne compilera pas (voir l'exemple live 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 clang? Ce rapport d'anomalie n'a même pas eu de proposition de résolution avant la fin de 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 serait un comportement indéfini car nous violons une obligation 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. [...]

Un comportement indé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:

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

Le code utilise opérateur virgule , l'opérande de gauche est une expression de valeur ignorée et est utilisé ici uniquement pour l'effet secondaire d'appeler std::cout. Le résultat de l'opérateur virgule est l'opérande droit qui dans ce cas est la valeur 195 Qui est affectée à la variable main.

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

main:
.zero   4

et les suivantes:

movl    $195, main(%rip)

Le scénario probable est que le programme passe au symbole main s'attendant à ce que du code valide soit là et dans certains cas seront des erreurs de segmentation . Donc, si tel est le cas, nous nous attendons à ce que le stockage d'un code machine valide dans la variable main puisse conduire à un programme réalisable , en supposant que nous sommes situés dans un segment qui permet l'exécution de code. Nous pouvons voir cette entrée IOCCC 1984 fait juste cela .

Il semble que nous pouvons demander à gcc de le faire en C en utilisant (voir en direct):

const int main = 195 ;

Il se bloque si la variable main n'est pas const probablement parce qu'elle n'est pas située dans un emplacement exécutable, Hat Tip to this comment here qui m'a donné cette idée.

Voir aussi réponse FUZxxl ici vers une version C spécifique de cette question.

83
Shafik Yaghmour

À partir du 3.6.1/1:

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 requis pour définir une fonction principale.

D'après cela, il semble que g ++ autorise un programme (vraisemblablement comme la 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 comme étant en ligne ou statique est mal informé. Le nom principal n'est pas autrement réservé.

Nous apprenons donc ici 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 du int main utilise l'opérateur virgule pour exécuter cout à l'initialisation statique, puis fournir une valeur intégrale réelle pour effectuer l'initialisation.

20
Mark B

gcc 4.8.1 génère l'assembly 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, pas dans la fonction main!

.zero 4 déclare 4 octets (initialisés à 0) 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 plate-forme.

9
sergej

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

De la norme:

.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.

8
R Sahu

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

Le format d'objet dans lequel ce translation-unit est compilé n'est pas capable de différencier un symbole de fonction et un symbole de variable.

Ainsi, le linker se relie avec bonheur au symbole (variable) main et le traite comme un appel de fonction. Mais pas avant que le système d'exécution ait 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é un seg-fault. Je suppose que c'est à ce moment-là que système d'exécution a essayé d'exécuter une variable int comme s'il s'agissait d'une fonction.

7
Galik

J'ai essayé cela sur un système d'exploitation Win7 64 bits utilisant VS2013 et il se compile correctement, mais lorsque j'essaie de créer l'application, j'obtiens ce message de 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 ==========
4
Francis Cugler