Antes de entrar en el meollo de la pregunta sobre lo que está sucediendo, es importante señalar que el programa está mal formado según el informe de defectos 1886: Enlace de idioma para main () :
[...] Un programa que declara una variable main en el ámbito global o que declara el nombre main con enlace en lenguaje C (en cualquier espacio de nombres) está mal formado. [...]
Las versiones más recientes de clang y gcc hacen que esto sea un error y el programa no se compilará ( vea el ejemplo en vivo de gcc ):
error: cannot declare '::main' to be a global variable
int main = ( std::cout << "C++ is excellent!\n", 195 );
^
Entonces, ¿por qué no había ningún diagnóstico en las versiones anteriores de gcc y clang? Este informe de defectos ni siquiera tuvo una propuesta de resolución hasta finales de 2014, por lo que este caso fue muy recientemente explícitamente mal formado, lo que requiere un diagnóstico.
Antes de esto, parece que esto sería un comportamiento indefinido, ya que estamos violando un deberán requisito del proyecto de C ++ estándar de la sección 3.6.1
[basic.start.main] :
Un programa debe contener una función global llamada main, que es el inicio designado del programa. [...]
El comportamiento indefinido es impredecible y no requiere un diagnóstico. La inconsistencia que vemos con la reproducción del comportamiento es un comportamiento indefinido típico.
Entonces, ¿qué está haciendo realmente el código y por qué en algunos casos produce resultados? Veamos lo que tenemos:
declarator
| initializer----------------------------------
| | |
v v v
int main = ( std::cout << "C++ is excellent!\n", 195 );
^ ^ ^
| | |
| | comma operator
| primary expression
global variable of type int
Tenemos main
cuál es un int declarado en el espacio de nombres global y se está inicializando, la variable tiene una duración de almacenamiento estática. La implementación define si la inicialización se llevará a cabo antes de que se realice un intento de llamada, main
pero parece que gcc hace esto antes de llamar.main
.
El código usa el operador de coma , el operando de la izquierda es una expresión de valor descartada y se usa aquí únicamente para el efecto secundario de la llamada std::cout
. El resultado del operador de coma es el operando derecho, que en este caso es el valor pr 195
que se asigna a la variable main
.
Podemos ver que Sergej señala el ensamblado generado muestra que cout
se llama durante la inicialización estática. Aunque el punto más interesante de discusión para ver la sesión de godbolt en vivo sería este:
main:
.zero 4
y el siguiente:
movl $195, main(%rip)
El escenario probable es que el programa salte al símbolo main
esperando que haya un código válido allí y, en algunos casos, se producirá un error de segmentación . Entonces, si ese es el caso, esperaríamos que almacenar código de máquina válido en la variable main
podría conducir a un programa viable , asumiendo que estamos ubicados en un segmento que permite la ejecución de código. Podemos ver que esta entrada del IOCCC de 1984 hace precisamente eso .
Parece que podemos hacer que gcc haga esto en C usando ( verlo en vivo ):
const int main = 195 ;
Se produce un error de segmentación si la variable main
no es constante, presumiblemente porque no está ubicada en una ubicación ejecutable, Hat Tip para este comentario aquí que me dio esta idea.
También vea la respuesta FUZxxl aquí a una versión específica de C de esta pregunta.