Considere el siguiente programa:
#include <iostream>
int main = ( std::cout << "C++ is excellent!\n", 195 );
Usando g ++ 4.8.1 (mingw64) en el sistema operativo Windows 7, el programa se compila y se ejecuta bien, imprimiendo:
¡C ++ es excelente!
a la consola. mainparece ser una variable global más que una función; ¿Cómo se puede ejecutar este programa sin la función main()? ¿Este código se ajusta al estándar C ++? ¿Está bien definido el comportamiento del programa? También utilicé la -pedantic-errorsopción, pero el programa aún se compila y se ejecuta.
c++
main
language-lawyer
Incinerador de basuras
fuente
fuente

195es el código de operación de laRETinstrucción y que en la convención de llamadas de C, el llamador borra la pila.main()función? De hecho, no tienen ninguna relación)int main = ( std::cout << "C++ is excellent!\n", exit(0),1 );(e incluido<cstdlib>), aunque el programa sigue estando legalmente mal formado.Respuestas:
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 () :
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 ):
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] :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:
Tenemos
maincuá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,mainpero 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 pr195que se asigna a la variablemain.Podemos ver que Sergej señala el ensamblado generado muestra que
coutse 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:y el siguiente:
El escenario probable es que el programa salte al símbolo
mainesperando 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 variablemainpodrí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 ):
Se produce un error de segmentación si la variable
mainno 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.
fuente
mainno es un identificador reservado (3.6.1 / 3). En este caso, creo que el manejo de VS2013 de este caso (ver la respuesta de Francis Cugler) es más correcto en su manejo que gcc & clang.Desde 3.6.1 / 1:
A partir de esto, parece que g ++ permite un programa (presumiblemente como la cláusula "independiente") sin una función principal.
Luego desde 3.6.1 / 3:
Así que aquí aprendemos que está perfectamente bien tener una variable entera nombrada
main.Finalmente, si se pregunta por qué se imprime la salida, la inicialización del
int mainusa el operador de coma para ejecutarcouten static init y luego proporcionar un valor integral real para realizar la inicialización.fuente
maina otra cosa:(.text+0x20): undefined reference toprincipal '' 'gcc 4.8.1 genera el siguiente ensamblado x86:
Tenga en cuenta que
coutse llama durante la inicialización, no en lamainfunción..zero 4declara 4 bytes (inicializados con 0) comenzando en la ubicaciónmain, dondemaines el nombre de la variable [!] .El
mainsímbolo se interpreta como el inicio del programa. El comportamiento depende de la plataforma.fuente
195es el código de operación deretalgunas arquitecturas. Por lo tanto, decir cero instrucciones puede no ser exacto.Ese es un programa mal formado. Se bloquea en mi entorno de prueba, cygwin64 / g ++ 4.9.3.
Del estándar:
fuente
La razón por la que creo que esto funciona es que el compilador no sabe que está compilando la
main()función, por lo que compila un entero global con efectos secundarios de asignación.El formato de objeto en el que se compila esta unidad de traducción no es capaz de diferenciar entre un símbolo de función y un símbolo de variable .
Entonces, el enlazador se vincula felizmente al símbolo principal (variable) y lo trata como una llamada a función. Pero no hasta que el sistema de ejecución haya ejecutado el código de inicialización de la variable global.
Cuando ejecuté la muestra, se imprimió pero luego causó una falla de segmentación . Supongo que fue entonces cuando el sistema de tiempo de ejecución intentó ejecutar una variable int como si fuera una función .
fuente
Probé esto en un sistema operativo Win7 de 64 bits usando VS2013 y se compila correctamente, pero cuando intento compilar la aplicación, aparece este mensaje en la ventana de salida.
fuente
main()porque es una variable de tipointEstás haciendo un trabajo complicado aquí. Como principal (de alguna manera) podría declararse un número entero. Usó el operador de lista para imprimir el mensaje y luego asignarle 195. Como dijo alguien a continuación, que no se siente cómodo con C ++, es cierto. Pero como el compilador no encontró ningún nombre definido por el usuario, main, no se quejó. Recuerde que main no es una función definida por el sistema, su función definida por el usuario y la cosa desde la cual el programa comienza a ejecutarse es el Módulo Principal, no main (), específicamente. De nuevo, main () es llamado por la función de inicio que es ejecutada por el cargador intencionalmente. Luego, todas sus variables se inicializan y, mientras se inicializan, se generan así. Eso es. El programa sin main () está bien, pero no es estándar.
fuente