2010-11-21 14 views
27

Por lo que puedo decir, puede iniciar toda la acción en un constructor cuando crea un objeto global. Entonces, ¿realmente necesitas una función main() en C++ o solo es heredada?¿Realmente necesitas un main() en C++?

Entiendo que podría considerarse una mala práctica hacerlo. Solo estoy preguntando por curiosidad.

+3

Interesante pregunta. Nunca consideré tener un solo objeto global. Como dices, mala práctica pero interesante de todos modos. –

+0

algo así se implementa en MFC donde tiene instancia única de 'CWinApp' – Andrey

+3

@CwinApp, MFC proporciona main/winmain para usted. Por lo tanto, todavía tiene una función main() en MFC. –

Respuesta

30

Si desea ejecutar su programa en una implementación alojada de C++, necesita una función main. Así es como se definen las cosas. Puedes dejarlo vacío si quieres, por supuesto. En el aspecto técnico de las cosas, el enlazador quiere resolver el símbolo main que se utiliza en la biblioteca de tiempo de ejecución (que no tiene ni idea de sus intenciones especiales de omitirlo, simplemente todavía emite una llamada). Si el Estándar especificó que main es opcional, entonces las implementaciones del curso podrían encontrar soluciones, pero eso tendría que ocurrir en un universo paralelo.

Si va con "La ejecución se inicia en el constructor de mi objeto global", tenga en cuenta que se configuró a muchos problemas relacionados con el orden de construcciones de objetos de ámbito de espacio de nombres definidos en diferentes unidades de traducción (Entonces, ¿qué es ? el punto de entrada? La respuesta es: ¡Usted tendrá múltiples puntos de entrada, y qué punto de entrada se ejecuta primero no está especificado!). En C++ 03 ni siquiera está garantizado que cout esté correctamente construido (en C++ 0x tiene la garantía de que lo es, antes de que cualquier código intente usarlo, siempre que haya un precedente incluido de <iostream>).

No tiene esos problemas y no necesita trabajar con ellos (lo que puede ser muy complicado) si comienza a ejecutar correctamente cosas en ::main.


Como se ha mencionado en los comentarios, no son sin embargo varios sistemas que se esconden main del usuario haciendo que le dijera el nombre de una clase que se emplea dentro de main. Esto funciona de forma similar al ejemplo siguiente

class MyApp { 
public: 
    MyApp(std::vector<std::string> const& argv); 

    int run() { 
     /* code comes here */ 
     return 0; 
    }; 
}; 

IMPLEMENT_APP(MyApp); 

Para el usuario de este sistema, que está completamente oculto que hay una función main, pero esa macro en realidad definir una función de este tipo principal de la siguiente

#define IMPLEMENT_APP(AppClass) \ 
    int main(int argc, char **argv) { \ 
    AppClass m(std::vector<std::string>(argv, argv + argc)); \ 
    return m.run(); \ 
    } 

Esto no tiene el problema del orden de construcción no especificado mencionado anteriormente. El beneficio de ellos es que trabajan con diferentes formas de puntos de entrada de nivel superior. Por ejemplo, los programas de Windows GUI se inician en una función WinMain - IMPLEMENT_APP podría definir dicha función en su lugar en esa plataforma.

+1

¿qué se aloja C++? –

+0

@bjarkef que necesitaría inventar una buena pregunta nueva (sin tener en cuenta los duplicados reales de eso). Pero en resumen: es una implementación en la que puede no haber compatibilidad con el sistema operativo para archivos, excepciones, etc. Es, por así decirlo, el mínimo indispensable. Un kernel de sistema operativo podría escribirse para ser dirigido a una implementación independiente. –

+0

Creo que C++ realmente podría usar una excepción 'SystemExit' como Python. Eso sería mucho más limpio que llamar a 'salir'. Pero, por supuesto, entonces usted tiene la cuestión de qué hilo arrojar la excepción dada realmente importaba. – Omnifarious

0

Existen implementaciones donde los objetos globales no son posibles, o donde los constructores no triviales no son posibles para tales objetos (especialmente en los dominios móviles e integrados).

+1

Buen punto, pero uno asumiría que está hablando de una implementación C++ más "normal". –

2

Si tiene más de un objeto global en construcción, no hay garantía de qué constructor se ejecutará primero.

3

En general, una aplicación necesita un punto de entrada, y main es ese punto de entrada. El hecho de que la inicialización de globales ocurra antes del main es bastante irrelevante. Si está escribiendo una consola o aplicación GUI, debe tener un main para que se vincule, y es una buena práctica que esa rutina sea responsable de la ejecución principal de la aplicación en lugar de utilizar otras funciones para fines extraños no deseados.

+0

Estoy de acuerdo, la inicialización de los objetos solo ocurre en dos puntos. En tiempo de carga, tirando EXE o DLL en la memoria, y en la inicialización de la pila donde se configura el punto de entrada (principal). – Eric

2

Si está compilando código de biblioteca estático o dinámico, entonces no necesita definir main usted mismo, pero seguirá ejecutándose en algún programa que lo tenga.

3

Bueno, desde la perspectiva del estándar C++, sí, aún se requiere. Pero sospecho que tu pregunta es de una naturaleza diferente a esa.

Creo que hacerlo de la manera que estás pensando causaría demasiados problemas.

Por ejemplo, en muchos entornos, el valor de retorno de main se da como el resultado del estado al ejecutar el programa como un todo. Y eso sería muy difícil de replicar de un constructor. Un poco de código todavía podría llamar al exit por supuesto, pero eso parece usar un goto y omitiría la destrucción de cualquier cosa en la pila. Puede tratar de arreglar las cosas teniendo una excepción especial que arrojó en su lugar para generar un código de salida que no sea 0.

Pero aún así se encuentra con el problema del orden de ejecución de los constructores globales que no están definidos. Eso significa que en cualquier constructor particular para un objeto global no podrás hacer suposiciones sobre si existe o no otro objeto global.

Puede intentar resolver el problema de orden del constructor diciendo que cada constructor obtiene su propio hilo, y si desea acceder a cualquier otro objeto global, debe esperar una variable de condición hasta que digan que están construidos. Sin embargo, eso es solo pedir bloqueos, y esos puntos muertos serían muy difíciles de depurar. También tendría el problema de qué hilo saliente con la excepción especial 'valor de retorno del programa' constituiría el valor real de retorno del programa como un todo.

Creo que esos dos problemas son asesinos si quieres deshacerte de main.

Y no puedo pensar en un lenguaje que no tenga un equivalente básico a main. En Java, por ejemplo, hay un nombre de clase suministrado externamente que es main se llama a la función estática. En Python, está el módulo __main__. En perl está el script que especifique en la línea de comando.

4

Sí! Puede deshacerse de main.

Descargo de responsabilidad: Ha preguntado si era posible, no si debería hacerse. Esta es una mala idea totalmente sin respaldo. Lo hice yo mismo, por razones que no abordaré, pero no lo estoy recomendando. Mi propósito no era deshacerme de main, pero también puede hacerlo.

Los pasos básicos son los siguientes:

  1. Encuentra crt0.c en el directorio fuente CRT de su compilador.
  2. Agregue crt0.c a su proyecto (una copia, no el original).
  3. Encuentra y elimina la llamada al principal desde crt0.c.

Hacer que compilar y vincular puede ser difícil; Qué difícil depende de qué compilador y qué versión del compilador.

Agregado

sólo lo hice con Visual Studio 2008, así que aquí están los pasos exactos que hay que tomar para conseguir que funcione con ese compilador.

  1. Cree una nueva aplicación de consola C++ Win32 (haga clic en siguiente y marque Empty Project).
  2. Agregar nuevo elemento ... Archivo C++, pero nómbrelo crt0.c (no .cpp).
  3. Copie los contenidos de C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src\crt0.c y péguelos en crt0.c.
  4. Encuentra mainret = _tmain(__argc, _targv, _tenviron); y coméntalo.
  5. Haga clic derecho en crt0.c y seleccione Propiedades.
  6. Establecer C/C++ -> General -> Directorios de inclusión adicionales = "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src".
  7. Establecer C/C++ -> Preprocesador -> Definiciones de preprocesador = _CRTBLD.
  8. Haga clic en Aceptar.
  9. Haga clic con el botón derecho en el nombre del proyecto y seleccione Propiedades.
  10. Establecer C/C++ -> Generación de código -> Biblioteca de tiempo de ejecución = Multi-threaded Debug (/MTd) (*).
  11. Haga clic en Aceptar.
  12. Agregar nuevo elemento ... Archivo C++, asígnele el nombre (app.cpp para este ejemplo).
  13. Pegue el siguiente código en app.cpp y ejecútelo.

(*) No puede usar la DLL de tiempo de ejecución, tiene que enlazar estáticamente a la biblioteca de tiempo de ejecución.

#include <iostream> 

class App 
{ 
    public: App() 
    { 
     std::cout << "Hello, World! I have no main!" << std::endl; 
    } 
}; 

static App theApp; 

Agregado

que eliminan la llamada de salida superfluo y la propaganda sobre la vida, ya que creo que todos somos capaces de entender las consecuencias de la eliminación principal.

Ultra Necro

me encontré con esta respuesta y leer tanto ella como las objeciones de John Dibling abajo. Resultó evidente que no expliqué lo que hace el procedimiento anterior y por qué eso elimina el programa principal del programa por completo.

John afirma que "siempre hay un problema principal" en el CRT. Esas palabras no son estrictamente correctas, pero el espíritu de la declaración sí lo es. Main no es una función proporcionada por el CRT, debe agregarlo usted mismo. La llamada a esa función está en la función de punto de entrada proporcionada por CRT.

El punto de entrada de cada programa C/C++ es una función en un módulo llamado 'crt0'. No estoy seguro de si esto es una convención o parte de la especificación del lenguaje, pero cada compilador C/C++ que he encontrado (que es mucho) lo usa.Esta función hace básicamente tres cosas:

  1. inicializar el CRT
  2. Call principal
  3. derribar

En el ejemplo anterior, se _tmain la llamada, pero eso es un poco de magia macro para permitir las diversas formas que puede tener 'principal', algunas de las cuales son VS específicas en este caso.

Lo que hace el procedimiento anterior es que elimina el módulo 'crt0' del CRT y lo reemplaza por uno nuevo. Esta es la razón por la que no puede usar la DLL de Runtime, ya existe una función en esa DLL con el mismo nombre de punto de entrada que la que estamos agregando (2). Cuando vincula estáticamente, el CRT es una colección de archivos .lib, y el enlazador le permite anular por completo los módulos .lib. En este caso, un módulo con una sola función.

Nuestro nuevo programa contiene el stock CRT, menos su módulo CRT0, pero con un módulo CRT0 de nuestra propia creación. Ahí eliminamos la llamada a main. ¡Así que no hay main en ninguna parte!

(2) Puede pensar que podría utilizar el DLL de tiempo de ejecución al cambiar el nombre de la función de punto de entrada en su archivo crt0.c y cambiar el punto de entrada en la configuración del enlazador. Sin embargo, el compilador desconoce el cambio del punto de entrada y el archivo DLL contiene una referencia externa a una función 'principal' que no está proporcionando, por lo que no compilaría.

+0

¡Me gusta mucho esto! – vy32

+0

Esto realmente no se deshace de principal. Main siempre vivió en el CRT aquí. '_tmain' es exactamente lo que se llama' main' de CRT. Ahora no hace eso. –

+0

@John: ¿Eh? Elimina la necesidad de que proporciones un método principal que sea el tema de la consulta original. Lo hace, no escondiendo un main en un lugar oscuro, sino deshaciéndose de la llamada CRT a main que es la fuente del 'problema' (el error del enlazador que normalmente obtendrías al no definir main). – Tergiver

2

Si está codificando para Windows, haga no haga esto.

Ejecutar su aplicación completamente desde el constructor de un objeto global puede funcionar bien durante bastante tiempo, pero tarde o temprano se realizará una llamada a la función incorrecta y se terminará con un programa que finaliza sin previo aviso.

  1. Los constructores de objetos globales se ejecutan durante el inicio del tiempo de ejecución de C.
  2. El código de inicio del tiempo de ejecución de C se ejecuta durante el DLL Principal de la DLL de tiempo de ejecución de C
  3. Durante DLLMain, mantiene el bloqueo del cargador de DLL.
  4. Tratar de cargar otra DLL mientras se mantiene el bloqueo del cargador de DLL resulta en una muerte rápida para su proceso.

La compilación de toda su aplicación en un solo archivo ejecutable no lo salvará; muchas llamadas Win32 tienen el potencial de cargar silenciosamente los archivos DLL del sistema.