c++ - valores - variables locales pdf
¿Cómo puede funcionar un programa con una variable global llamada main en lugar de una función main? (7)
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 compila y funciona bien, imprimiendo:
C ++ es excelente!
a la consola
main
parece ser una variable global en lugar de una función;
¿Cómo se puede ejecutar este programa sin la función
main()
?
¿Este código se ajusta al estándar C ++?
¿El comportamiento del programa está bien definido?
También he usado la opción
-pedantic-errors
pero el programa aún se compila y ejecuta.
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 de 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 de gcc live ):
error: cannot declare ''::main'' to be a global variable
int main = ( std::cout << "C++ is excellent!/n", 195 );
^
Entonces, ¿por qué no había diagnóstico en versiones anteriores de gcc y clang? Este informe de defectos ni siquiera tenía una resolución propuesta hasta fines de 2014 y, por lo tanto, este caso solo fue explícitamente mal formado recientemente, lo que requiere un diagnóstico.
Antes de esto, parece que este sería un comportamiento indefinido ya que estamos violando un requisito del borrador del estándar C ++ de la sección
3.6.1
[basic.start.main]
:
Un programa contendrá 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 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
que es un
int
declarado en el espacio de nombres global y se está inicializando, la variable tiene una duración de almacenamiento estático.
La implementación define si la inicialización tendrá lugar antes de que se intente llamar a
main
pero parece que gcc lo hace antes de llamar a
main
.
El código usa el
operador de coma
, el operando izquierdo es una expresión de valor descartado y se usa aquí únicamente para el efecto secundario de llamar a
std::cout
.
El resultado del operador de coma es el operando correcto, que en este caso es el prvalue
195
que se asigna a la variable
main
.
Podemos ver que
sergej señala que
el ensamblaje generado muestra que se llama a
cout
durante la inicialización estática.
Aunque el punto de discusión más interesante para
ver la sesión de Godbolt en vivo
sería este:
main:
.zero 4
y el subsiguiente:
movl $195, main(%rip)
El escenario probable es que el programa salta al símbolo
main
esperando que haya un código válido y, en
algunos casos, tendrá una falla seg
.
Entonces, si ese es el caso, esperaríamos almacenar un código de máquina válido en la variable
main
podría conducir a un
programa viable
, suponiendo que estemos ubicados en un segmento que permite la ejecución del código.
Podemos ver que
esta entrada de IOCCC de 1984
hace
exactamente eso
.
Parece que podemos obtener gcc para hacer esto en C usando ( verlo en vivo ):
const int main = 195 ;
Seg-falla si la variable
main
no es constante probablemente porque no se encuentra en una ubicación ejecutable, Hat Tip a este
comentario aquí
que me dio esta idea.
Vea también la respuesta de FUZxxl aquí a una versión C específica de esta pregunta.
Desde 3.6.1 / 1:
Un programa contendrá una función global llamada main, que es el inicio designado del programa. La implementación se define si se requiere un programa en un entorno independiente para definir una función principal.
A partir de esto, parece que g ++ permite un programa (presumiblemente como la cláusula "independiente") sin una función principal.
Luego de 3.6.1 / 3:
La función main no se utilizará (3.2) dentro de un programa. El enlace (3.5) de main está definido en la implementación. Un programa que declara que main es inline o static está mal formado. El nombre principal no está reservado de otra manera.
Así que aquí aprendemos que está perfectamente bien tener una variable entera llamada
main
.
Finalmente, si se pregunta por qué se imprime la salida, la inicialización de
int main
usa el operador de coma para ejecutar
cout
en init estático y luego proporciona un valor integral real para hacer la inicialización.
Ese es un programa mal formado. Se bloquea en mi entorno de prueba, cygwin64 / g ++ 4.9.3.
De la norma:
3.6.1 Función principal [basic.start.main]
1 Un programa contendrá una función global llamada main, que es el inicio designado del programa.
Estás haciendo un trabajo complicado aquí. Como principal (de alguna manera) podría declararse como entero. Usó el operador de lista para imprimir el mensaje y luego le asignó 195. Como dijo alguien a continuación, que no se consuela 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 Main Module, no main (), específicamente. Nuevamente, 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 inicializa, se genera así. Eso es. El programa sin main () está bien, pero no es estándar.
He intentado esto en un sistema operativo Win7 de 64 bits con VS2013 y se compila correctamente, pero cuando intento compilar la aplicación, recibo este mensaje desde la ventana de salida.
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 ==========
La razón por la que creo que esto funciona es que el
compilador
no sabe que está compilando la función
main()
, 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 felizmente se vincula al símbolo principal (variable) y lo trata como una llamada a la función. Pero no hasta que el sistema de tiempo 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 seg . Supongo que es cuando el sistema de tiempo de ejecución intentó ejecutar una variable int como si fuera una función .
gcc 4.8.1 genera el siguiente ensamblado x86:
.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
Tenga en cuenta que
cout
se llama durante la inicialización, ¡no en la función
main
!
.zero 4
declara 4 bytes (inicializados en 0) comenzando en la ubicación
main
, donde
main
es el nombre de la
variable [!]
.
El símbolo
main
se interpreta como el inicio del programa.
El comportamiento depende de la plataforma.