Prima di entrare nel vivo della domanda su cosa sta succedendo, è importante sottolineare che il programma è mal formato secondo il rapporto sui difetti 1886: Language linkage for main () :
[...] Un programma che dichiara una variabile main a livello globale o che dichiara il nome main con collegamento al linguaggio C (in qualsiasi spazio dei nomi) è mal formato. [...]
Le versioni più recenti di clang e gcc rendono questo un errore e il programma non verrà compilato ( vedi l'esempio live di gcc ):
error: cannot declare '::main' to be a global variable
int main = ( std::cout << "C++ is excellent!\n", 195 );
^
Allora perché non c'era diagnostica nelle versioni precedenti di gcc e clang? Questo rapporto di difetto non aveva nemmeno una proposta di risoluzione fino alla fine del 2014 e quindi questo caso è stato solo di recente esplicitamente mal formato, il che richiede una diagnosi.
Prima di questo, sembra che questo sarebbe un comportamento indefinito poiché stiamo violando una deve requisito del progetto di standard C ++ dalla sezione 3.6.1
[basic.start.main] :
Un programma deve contenere una funzione globale chiamata main, che è l'inizio designato del programma. [...]
Il comportamento indefinito è imprevedibile e non richiede una diagnosi. L'incoerenza che vediamo con la riproduzione del comportamento è un tipico comportamento non definito.
Quindi cosa fa effettivamente il codice e perché in alcuni casi produce risultati? Vediamo cosa abbiamo:
declarator
| initializer----------------------------------
| | |
v v v
int main = ( std::cout << "C++ is excellent!\n", 195 );
^ ^ ^
| | |
| | comma operator
| primary expression
global variable of type int
Abbiamo main
che è un int dichiarato nello spazio dei nomi globale e viene inizializzato, la variabile ha una durata di archiviazione statica. L'implementazione definisce se l'inizializzazione avverrà prima che main
venga effettuato un tentativo di chiamata, ma sembra che gcc lo faccia prima di chiamare main
.
Il codice utilizza l' operatore virgola , l'operando sinistro è un'espressione di valore scartato e viene utilizzato qui esclusivamente per l'effetto collaterale della chiamata std::cout
. Il risultato dell'operatore virgola è l'operando di destra che in questo caso è il prvalue 195
assegnato alla variabile main
.
Possiamo vedere sergej che indica che l'assembly generato mostra che cout
viene chiamato durante l'inizializzazione statica. Anche se il punto più interessante per la discussione vedere la sessione live di godbolt sarebbe questo:
main:
.zero 4
e il successivo:
movl $195, main(%rip)
Lo scenario probabile è che il programma salti al simbolo main
aspettandosi che ci sia un codice valido e in alcuni casi seg-fault . Quindi, se questo è il caso, ci aspetteremmo che la memorizzazione di un codice macchina valido nella variabile main
possa portare a un programma funzionante , supponendo che ci troviamo in un segmento che consente l'esecuzione del codice. Possiamo vedere che questa voce IOCCC del 1984 fa proprio questo .
Sembra che possiamo convincere gcc a farlo in C usando ( guardalo dal vivo ):
const int main = 195 ;
È seg-faults se la variabile main
non è const presumibilmente perché non si trova in una posizione eseguibile, Hat Tip a questo commento qui che mi ha dato questa idea.
Vedi anche la risposta di FUZxxl qui a una versione specifica in C di questa domanda.