¿Cómo es posible no declarar nada dentro de main () en C ++ y aún tener una aplicación funcional después de la compilación?

86

En una entrevista me enfrenté a una pregunta como esta:

Su amigo le ha dado un archivo de código fuente único que imprime los números de Fibonacci en la consola. Tenga en cuenta que el bloque main () está vacío y no tiene declaraciones en su interior.

Explique cómo es posible (pista: instancia global!)

Realmente quiero saber sobre esto, ¡cómo puede ser posible algo así!

Rika
fuente
26
¡Mira la pista!
R. Martinho Fernandes
14
Porque es algo de lo que 1) no había oído hablar, 2) es una trivia útil porque la gente lo pregunta en las entrevistas, 3) una aplicación interesante del lenguaje para saberlo 4) Puedo reconocerlo y apuñalar a cualquiera en la cara con un cuchillo oxidado si los veo realmente usándolo en el código de producción.
Entidad Omnipotente
4
Un programador de C ++ competente y profesional sabrá la respuesta a esta pregunta. Si el propósito de esta pregunta de entrevista es determinar si la persona entrevistada es un programador de C ++ profesional y competente, entonces la pregunta no debería darles la respuesta.
John Dibling
1
En una entrevista, una alternativa sería tener la lógica dentro de cualquier función en el código y registrar la salida usando asserto #pragma messageetc. Esto redirigirá la salida a la consola durante la compilación. Es posible que el programa nunca se compile por completo, pero esta es sin duda una forma divertida de mostrar su pensamiento "original" durante la entrevista. Esto satisface la pregunta citada ya que NO menciona nada sobre la generación de binarios; más bien solo habla de un archivo C que puede mostrar "cosas" en la consola. ;-)
TheCodeArtist
1
¿Fue una entrevista para el IOCC ? :-) Ok, admito que lo hago a menudo para inicializar mis fábricas o ejecutar algún código de prueba. Por cierto, ' archivo de código fuente único ' también es una pista, que la entrada-pinta (principal por defecto) no es reemplazada por enlazador.
Valentin Heinitz

Respuestas:

127

Lo más probable es que se implemente como (o una variante de él):

 void print_fibs() 
 {
       //implementation
 }

 int ignore = (print_fibs(), 0);

 int main() {}

En este código, la variable global ignoredebe inicializarse antes de entrar en main()función. Ahora, para inicializar el global, print_fibs()debe ejecutarse donde pueda hacer cualquier cosa; en este caso, calcular los números de Fibonacci e imprimirlos. Algo similar que he mostrado en la siguiente pregunta (que había preguntado hace mucho tiempo):

Tenga en cuenta que dicho código no es seguro y es mejor evitarlo en general. Por ejemplo, es posible que el std::coutobjeto no se inicialice cuando print_fibs()se ejecuta, si es así, ¿qué haría std::couten la función? Sin embargo, si en otras circunstancias, no depende de dicho orden de inicialización, entonces es seguro llamar a las funciones de inicialización (que es una práctica común en C y C ++).

Nawaz
fuente
3
@Nawaz Probablemente valga la pena citar las garantías exactas. Se garantiza que los objetos dentro de una unidad de traducción se inicializarán en orden. Se garantiza que los objetos de flujo estándar se inicializarán antes o durante la primera inicialización de un std::ios_base::Initobjeto. Y <iostream>se garantiza que se comportará "como si" contuviera una instancia de un std::ios_base_Initobjeto en el ámbito del espacio de nombres.
James Kanze
3
@ Steve314: No devuelve nada, por eso he usado el operador de coma, para asegurarme de que el tipo de toda la expresión (print_fibs(), 0)sea int. Aquí está la demostración en línea .
Nawaz
1
@Nawaz Una alternativa a la función void y el operador de coma sería devolver a bool, y la variable bool fibsPrinted. Probablemente sea un poco más limpio si la función solo sirve aquí. (Pero la diferencia probablemente no sea suficiente para preocuparse.)
James Kanze
1
+1, habla de lo increíble. Tuve que unirme a stackoverflow solo para votar esta pregunta y esta respuesta.
Punto fijo
1
@Nawaz No estoy seguro de cuál es tu punto. La definición de std::coutestá en algún lugar de la biblioteca. Pero como ya he señalado, el estándar requiere que se inicialice antes de std::ios_base::Initque finalice el primer constructor de un objeto, y requiere que la inclusión se <iostream>comporte como si un std::ios_base::Initobjeto estuviera definido en el ámbito del espacio de nombres. Si la unidad de traducción incluye <iostream>antes de la definición del objeto que se inicializa, std::coutse garantiza que se construirá.
James Kanze
18

Espero que esto ayude

class cls
{
  public:
    cls()
    {
      // Your code for fibonacci series
    }
} objCls;

int main()
{
}

Entonces, tan pronto como se declara una variable global de la clase, se llama al constructor y allí agrega la lógica para imprimir la serie de Fibonacci.

Saksham
fuente
9

Sí, es posible. Necesita declarar una instancia global de un objeto que calcula los números de Fibonacci en el constructor del objeto.

Señor cerveza
fuente
6
Necesita declarar una instancia global de un objeto cuyo inicializador calcula los números de Fibonacci.
James Kanze
4

Conozco algunos ejemplos como ese que cuentas. Una forma de conseguirlo es utilizando la plantilla de metaprogramación. Utilizándolo, puede mover algún proceso de cálculo a la compilación.

Aquí puede obtener un ejemplo con los números de Fibonacci

Si lo usa en un constructor de clase estática y puede escribir los números sin necesidad de escribir ningún código en la función principal.

Espero que te ayude.

superar
fuente
3

Pueden suceder cosas durante la inicialización de variables globales / estáticas. El código se activará al iniciar la aplicación.

log0
fuente
3

Todos los constructores [*] para objetos de alcance de archivo se llaman antes de llegar main, al igual que todas las expresiones de inicializador para variables de alcance de archivo que no son de objeto.

Editar: Además, todos los destructores [*] para todos los objetos de alcance de archivo se llaman en orden inverso de construcción después de las mainsalidas. Teóricamente, podría poner su programa de Fibonacci en el destructor de un objeto.

[*] Tenga en cuenta que 'todos' ignora el comportamiento de cargar y descargar dinámicamente bibliotecas con las que su programa no estaba vinculado directamente. Sin embargo, aquellos técnicamente están fuera del lenguaje base C ++.

Joe Z
fuente
Todo ? ¿Incluso aquellos en dll que se cargan explícitamente después main?
James Kanze
Bueno, C ++ no define técnicamente bibliotecas cargadas dinámicamente, por lo que dentro de C ++ puro, mi declaración es correcta. Por lo tanto, sombree "Todo, excepto para inicializadores y objetos de alcance de archivo contenidos en DLL / DSO cargados después de llegar a main". En este caso, mainestá vacío, por lo que esos DLL / DSO tendrían que ser cargados por destructores, lo cual es muy perverso. Pero, siendo esto de la informática, supongo que deberíamos tener cuidado con palabras como "todos".
Joe Z
Agregué una advertencia sobre 'todos' a mi respuesta anterior, y también agregué una nota sobre dtors.
Joe Z
Sí, pero espero que eso suceda. Pre C ++ 11 contenía algunas palabras comadrejas destinadas a permitir DLL, pero que en la práctica solo significaba que, técnicamente, la garantía no siempre estaba ahí, a pesar de que estaba en todas las implementaciones reales, y mucho código dependía de ello. C ++ 11 solucionó eso, al menos.
James Kanze