¿Es main () realmente el inicio de un programa C ++?

131

La sección $ 3.6.1 / 1 del Estándar C ++ dice:

Un programa contendrá una función global llamada main , que es el inicio designado del programa.

Ahora considere este código,

int square(int i) { return i*i; }
int user_main()
{ 
    for ( int i = 0 ; i < 10 ; ++i )
           std::cout << square(i) << endl;
    return 0;
}
int main_ret= user_main();
int main() 
{
        return main_ret;
}

Este código de muestra hace lo que pretendo que haga, es decir, imprimir el cuadrado de enteros de 0 a 9, antes de ingresar a la main()función que se supone que es el "inicio" del programa.

También lo compilé con la -pedanticopción, GCC 4.5.0. ¡No da ningún error, ni siquiera una advertencia!

Entonces mi pregunta es,

¿Es este código realmente conforme con el estándar?

Si es estándar, ¿no invalida lo que dice el estándar? main()No es el inicio de este programa! user_main()ejecutado antes de la main().

Entiendo que para inicializar la variable global main_ret, use_main()primero se ejecuta, pero eso es algo completamente diferente; el punto es que, que no invalida la declaración citada 3.6.1 $ / 1 de la norma, ya que main()no es el inicio del programa; ¡De hecho es el final de este programa!


EDITAR:

¿Cómo define la palabra 'inicio'?

Se reduce a la definición de la frase "inicio del programa" . Entonces, ¿cómo lo define exactamente?

Nawaz
fuente

Respuestas:

85

No, C ++ hace muchas cosas para "configurar el entorno" antes de la llamada de main; sin embargo, main es el inicio oficial de la parte "especificada por el usuario" del programa C ++.

Parte de la configuración del entorno no es controlable (como el código inicial para configurar std :: cout; sin embargo, parte del entorno es controlable como bloques globales estáticos (para inicializar variables globales estáticas). Tenga en cuenta que dado que no tiene control antes de main, no tiene control total sobre el orden en que se inicializan los bloques estáticos.

Después de main, su código está conceptualmente "totalmente en control" del programa, en el sentido de que puede especificar las instrucciones que se realizarán y el orden en el que se ejecutarán. El subprocesamiento múltiple puede reorganizar el orden de ejecución del código; pero aún tiene el control de C ++ porque especificó que las secciones de código se ejecuten (posiblemente) fuera de orden.

Edwin Buck
fuente
9
+1 para este "Tenga en cuenta que dado que no tiene control total antes de main, no tiene control total sobre el orden en que se inicializan los bloques estáticos. Después de main, su código está conceptualmente" totalmente en control "de el programa, en el sentido de que ambos pueden especificar las instrucciones que se realizarán y el orden en que se realizarán " . Esto también me hace marcar esta respuesta como respuesta aceptada ... Creo que estos son puntos muy importantes, que justifica suficientemente main()como "inicio del programa"
Nawaz
13
@Nawaz: tenga en cuenta que, además de no tener control total sobre el orden de inicialización, no tiene control sobre los errores de inicialización: no puede detectar excepciones a nivel global.
André Caron
@Nawaz: ¿Qué son los bloques globales estáticos? ¿Podrías explicarlo con un ejemplo simple? Gracias
Destructor
@meet: los objetos declarados a nivel de espacio de nombres tienen staticuna duración de almacenamiento y, como tal, estos objetos que pertenecen a diferentes unidades de traducción se pueden inicializar en cualquier orden (porque el orden no está especificado por el estándar). No estoy seguro de si eso responde a su pregunta, aunque eso es lo que podría decir en el contexto de este tema.
Nawaz
88

Estás leyendo la oración incorrectamente.

Un programa contendrá una función global llamada main, que es el inicio designado del programa.

El estándar está definiendo la palabra "inicio" para los fines del resto del estándar. No dice que no se ejecuta ningún código antes de que mainse llame. Dice que el inicio del programa se considera en la función main.

Tu programa es compatible. Su programa no se ha "iniciado" hasta que se inicia main. Se llama al constructor antes de que su programa "comience" de acuerdo con la definición de "inicio" en el estándar, pero eso apenas importa. Una gran cantidad de código se ejecuta antes de mainque se Alguna vez llamada en todos los programas, no sólo este ejemplo.

Para fines de discusión, su código de constructor se ejecuta antes del 'inicio' del programa, y ​​eso es totalmente compatible con el estándar.

Adam Davis
fuente
3
Lo siento, pero no estoy de acuerdo con su interpretación de esa cláusula.
Carreras de ligereza en órbita
Creo que Adam Davis tiene razón, "principal" se parece más a algún tipo de restricciones de codificación.
laike9m
@LightnessRacesinOrbit Nunca hice un seguimiento, pero para mí esa oración se puede reducir lógicamente a "una función global llamada main es el inicio designado del programa" (énfasis agregado). ¿Cuál es tu interpretación de esa oración?
Adam Davis
1
@ AdamDavis: No recuerdo cuál era mi preocupación. No puedo pensar en uno ahora.
Carreras de ligereza en órbita
23

Su programa no se vinculará y, por lo tanto, no se ejecutará a menos que haya un main. Sin embargo, main () no causa el inicio de la ejecución del programa porque los objetos a nivel de archivo tienen constructores que se ejecutan de antemano y sería posible escribir un programa completo que ejecute su vida útil antes de alcanzar main () y dejar que main tenga Un cuerpo vacío.

En realidad, para hacer cumplir esto, tendría que tener un objeto construido antes de main y su constructor para invocar todo el flujo del programa.

Mira este:

class Foo
{
public:
   Foo();

 // other stuff
};

Foo foo;

int main()
{
}

El flujo de su programa se derivaría efectivamente de Foo::Foo()

CashCow
fuente
13
+1. Pero tenga en cuenta que si tiene múltiples objetos globales en diferentes unidades de traducción, esto lo meterá en problemas rápidamente ya que el orden en que se llaman los constructores no está definido. Puede salirse con singletons e inicialización perezosa, pero en un entorno multiproceso, las cosas se ponen muy feas rápidamente. En una palabra, no hagas esto en código real.
Alexandre C.
3
Si bien probablemente debería darle a main () un cuerpo adecuado en su código y permitirle ejecutar la ejecución, el concepto de objetos fuera de ese inicio es en lo que se basan muchas bibliotecas LD_PRELOAD.
CashCow
2
@Alex: El estándar dice indefinido, pero como una cuestión práctica, el orden de enlace (generalmente, dependiendo del compilador) controla el orden de iniciación.
ThomasMcLeod
1
@Thomas: Seguramente ni siquiera trataría remotamente de confiar en eso. Tampoco trataría de controlar manualmente el sistema de compilación.
Alexandre C.
1
@Alex: ya no es tan importante, pero en el pasado usaríamos el orden de enlace para controlar la imagen de compilación para disminuir la paginación de memoria física. Hay otras razones secundarias por las que es posible que desee controlar el orden de inicio, incluso cuando no afecta la semántica del programa, como las pruebas de comparación de rendimiento de inicio.
ThomasMcLeod
15

También etiquetó la pregunta como "C", luego, hablando estrictamente sobre C, su inicialización debería fallar según la sección 6.7.8 "Inicialización" del estándar ISO C99.

Lo más relevante en este caso parece ser la restricción # 4 que dice:

Todas las expresiones en un inicializador para un objeto que tiene una duración de almacenamiento estático serán expresiones constantes o literales de cadena.

Entonces, la respuesta a su pregunta es que el código no cumple con el estándar C.

Probablemente desee eliminar la etiqueta "C" si solo le interesa el estándar C ++.

Remo.D
fuente
44
@ Remo.D, ¿podría decirnos qué hay en esa sección? No todos tenemos el estándar C :).
UmmaGumma
2
Ya que eres tan exigente: Por desgracia, ANSI C ha quedado obsoleto desde 1989. ISO C90 o C99 son los estándares relevantes para citar.
Lundin
@Lundin: Nadie es lo suficientemente exigente :) Estaba leyendo ISO C99 pero estoy bastante seguro de que también se aplica a C90.
Remo.D
@Un tiro. Tienes razón, agregó la oración que creo que es más relevante aquí.
Remo.D
3
@Remo: +1 por proporcionar la información de que no es válida C; No lo sabia. ¡Mira cómo aprende la gente, a veces por plan, a veces por casualidad!
Nawaz
10

La sección 3.6 en su conjunto es muy clara sobre la interacción mainy las inicializaciones dinámicas. El "inicio designado del programa" no se utiliza en ningún otro lugar y es solo descriptivo de la intención general de main(). No tiene ningún sentido interpretar esa frase de una manera normativa que contradiga los requisitos más detallados y claros de la Norma.

aschepler
fuente
9

El compilador a menudo tiene que agregar código antes que main () para cumplir con el estándar. Debido a que el estándar especifica que la inicialización de las variables globales / estáticas debe hacerse antes de ejecutar el programa. Y como se mencionó, lo mismo ocurre con los constructores de objetos ubicados en el alcance del archivo (globales).

Por lo tanto, la pregunta original también es relevante para C, porque en un programa en C todavía tendría que hacer la inicialización global / estática antes de que se pueda iniciar el programa.

Los estándares suponen que estas variables se inicializan a través de "magia", porque no dicen cómo deben establecerse antes de la inicialización del programa. Creo que lo consideraron como algo fuera del alcance de un estándar de lenguaje de programación.

Editar: Ver por ejemplo ISO 9899: 1999 5.1.2:

Todos los objetos con una duración de almacenamiento estático se inicializarán (se establecerán en sus valores iniciales) antes del inicio del programa. La manera y el momento de dicha inicialización no están especificados.

La teoría detrás de cómo se iba a hacer esta "magia" se remonta al nacimiento de C, cuando era un lenguaje de programación destinado a ser utilizado solo para el sistema operativo UNIX, en computadoras basadas en RAM. En teoría, el programa podría cargar todos los datos preinicializados del archivo ejecutable en la RAM, al mismo tiempo que el programa mismo se cargó en la RAM.

Desde entonces, las computadoras y el sistema operativo han evolucionado, y C se utiliza en un área mucho más amplia de lo previsto originalmente. Un SO de PC moderno tiene direcciones virtuales, etc., y todos los sistemas integrados ejecutan código desde ROM, no RAM. Por lo tanto, hay muchas situaciones en las que la RAM no se puede configurar "automáticamente".

Además, el estándar es demasiado abstracto para saber algo sobre pilas y memoria de proceso, etc. Estas cosas también deben hacerse antes de que se inicie el programa.

Por lo tanto, casi todos los programas de C / C ++ tienen algún código de inicio / "copia" que se ejecuta antes de que se llame a main, para cumplir con las reglas de inicialización de los estándares.

Como ejemplo, los sistemas embebidos suelen tener una opción llamada "inicio no compatible con ISO" donde se omite toda la fase de inicialización por razones de rendimiento, y luego el código realmente comienza directamente desde main. Pero tales sistemas no se ajustan a los estándares, ya que no puede confiar en los valores de inicio de las variables globales / estáticas.

Lundin
fuente
4

Su "programa" simplemente devuelve un valor de una variable global. Todo lo demás es código de inicialización. Por lo tanto, el estándar se cumple: solo tiene un programa muy trivial y una inicialización más compleja.

Zac Howland
fuente
2

Parece una objeción semántica inglesa. El OP se refiere a su bloque de código primero como "código" y luego como "programa". El usuario escribe el código y luego el compilador escribe el programa.

dSerk
fuente
1

Se llama a main después de inicializar todas las variables globales.

Lo que el estándar no especifica es el orden de inicialización de todas las variables globales de todos los módulos y bibliotecas enlazadas estáticamente.

vz0
fuente
0

Sí, main es el "punto de entrada" de cada programa C ++, excepto las extensiones específicas de la implementación. Aun así, algunas cosas suceden antes de main, especialmente la inicialización global, como por ejemplo main_ret.

Fred Nurk
fuente