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 () :
[...] 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 se compilera pas ( voir l'exemple gcc live ):
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'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 programme doit contenir une fonction globale appelée main, qui est le début désigné du programme. [...]
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:
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 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 ne 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 std::cout
. Le résultat de l'opérateur virgule est l'opérande de droite qui, dans ce cas, est la prvalue 195
qui est affectée à la variable main
.
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:
main:
.zero 4
et le suivant:
movl $195, main(%rip)
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 variable main
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 ):
const int main = 195 ;
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.