Come è possibile non dichiarare nulla all'interno di main () in C ++ e avere un'applicazione funzionante dopo la compilazione?


86

In un'intervista mi sono trovato di fronte a una domanda come questa:

Il tuo amico ti ha dato un unico file di codice sorgente che stampa i numeri di Fibonacci sulla console. Nota che il blocco main () è vuoto e non contiene alcuna istruzione al suo interno.

Spiega come è possibile (suggerimento: istanza globale!)

Voglio davvero sapere su questo, come una cosa del genere può essere anche possibile!


26
Guarda il suggerimento!
R. Martinho Fernandes

14
Perché è qualcosa di cui 1) non avevo sentito parlare, 2) è utile banalità perché le persone lo chiedono nelle interviste, 3) un'applicazione interessante del linguaggio da conoscere in modo che 4) io possa riconoscerlo e pugnalare chiunque in faccia con un coltello arrugginito se li vedo effettivamente usarlo nel codice di produzione.
OnnipotentEntity

4
Un programmatore C ++ competente e professionale conoscerà la risposta a questa domanda. Se lo scopo di questa domanda dell'intervista è determinare se la persona intervistata è un programmatore C ++ competente e professionale, la domanda non dovrebbe dargli la risposta.
John Dibling

1
In un'impostazione di intervista, un'alternativa sarebbe avere la logica all'interno di qualsiasi funzione nel codice e registrare l'output usando asserto #pragma messageecc. Questo reindirizzerà l'output alla console durante la compilazione. Il programma potrebbe non essere mai compilato completamente, ma questo è sicuramente un modo divertente per mostrare il tuo pensiero "fuori dagli schemi" durante l'intervista. Ciò soddisfa la domanda citata in quanto NON menziona nulla sul binario generato; piuttosto parla solo di un file C che può visualizzare "cose" sulla console. ;-)
TheCodeArtist

1
Era un'intervista per IOCC ? :-) Ok, ammetto che lo faccio spesso per inizializzare le mie fabbriche o eseguire un codice di test. A proposito, anche ' singolo file di codice sorgente' è un suggerimento che la entry-pint (main per impostazione predefinita) non è sostituita dal linker.
Valentin Heinitz

Risposte:


127

Molto probabilmente è implementato come (o una sua variante):

 void print_fibs() 
 {
       //implementation
 }

 int ignore = (print_fibs(), 0);

 int main() {}

In questo codice, la variabile globale ignoredeve essere inizializzata prima di entrare in main()funzione. Ora, per inizializzare il globale, print_fibs()deve essere eseguito dove puoi fare qualsiasi cosa: in questo caso, calcola i numeri di Fibonacci e stampali! Una cosa simile che ho mostrato nella seguente domanda (che avevo chiesto tempo fa):

Notare che tale codice non è sicuro e dovrebbe essere evitato in generale. Ad esempio, l' std::coutoggetto potrebbe non essere inizializzato quando print_fibs()viene eseguito, in tal caso cosa farebbe std::coutnella funzione? Tuttavia, se in altre circostanze, non dipende da tale ordine di inizializzazione, è sicuro chiamare le funzioni di inizializzazione (che è una pratica comune in C e C ++).


3
@Nawaz Probabilmente vale la pena citare le esatte garanzie. È garantito che gli oggetti all'interno di un'unità di traduzione siano inizializzati in ordine. È garantito che gli oggetti stream standard vengano inizializzati prima o durante la prima inizializzazione di un std::ios_base::Initoggetto. Ed <iostream>è garantito che si comporti "come se" contenesse un'istanza di un std::ios_base_Initoggetto nell'ambito dello spazio dei nomi.
James Kanze

3
@ Steve314: Non restituisce nulla, motivo per cui ho utilizzato l'operatore virgola, per assicurarmi che il tipo dell'intera espressione (print_fibs(), 0)sia int. Ecco la demo online .
Nawaz

1
@Nawaz Un'alternativa alla funzione void e all'operatore virgola sarebbe restituire a bool, e la variabile bool fibsPrinted. Probabilmente è leggermente più pulito se la funzione serve solo qui. (Ma la differenza probabilmente non è sufficiente per preoccuparsi.)
James Kanze

1
+1, Parla di fantastico. Ho dovuto unirmi a stackoverflow solo per votare questa domanda e questa risposta.
Punto fisso

1
@Nawaz non sono sicuro di quale sia il tuo punto. La definizione di std::coutè da qualche parte nella libreria. Ma come ho già sottolineato, lo standard richiede che venga inizializzato prima del termine del primo costruttore di un std::ios_base::Initoggetto e richiede che l'inclusione si <iostream>comporti come se un std::ios_base::Initoggetto fosse definito nell'ambito dello spazio dei nomi. Se l'unità di traduzione include <iostream>prima della definizione dell'oggetto da inizializzare, std::coutè garantito che sia costruita.
James Kanze

18

Spero che sia di aiuto

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

int main()
{
}

Quindi, non appena viene dichiarata una variabile globale della classe, viene chiamato il costruttore e lì si aggiunge la logica per stampare la serie di Fibonacci.


9

Sì, è possibile. È necessario dichiarare un'istanza globale di un oggetto che calcola i numeri di Fibonacci nel costruttore dell'oggetto.


6
È necessario dichiarare un'istanza globale di un oggetto il cui inizializzatore calcola i numeri di Fibonacci.
James Kanze

4

Conosco alcuni esempi come quello che dici. Un modo per ottenerlo è utilizzare la metaprogrammazione del modello. Usandolo puoi spostare alcuni processi di calcolo nella compilazione.

Qui puoi ottenere un esempio con i numeri di Fibonacci

Se lo usi in un costruttore di classi statiche e puoi scrivere i numeri senza bisogno di scrivere alcun codice nella funzione principale.

Spero ti aiuti.


3

Le cose possono accadere durante l'inizializzazione delle variabili globali / statiche. Il codice verrà attivato all'avvio dell'applicazione.


3

Tutti i [*] costruttori per oggetti con ambito di file vengono chiamati prima di raggiungerli main, così come tutte le espressioni di inizializzazione per variabili con ambito di file non oggetto.

Modifica: inoltre, tutti i distruttori [*] per tutti gli oggetti con ambito file vengono chiamati in ordine inverso rispetto alla costruzione dopo le mainuscite. In teoria potresti mettere il tuo programma Fibonacci nel distruttore di un oggetto.

[*] Nota che "all" ignora il comportamento di caricamento e scaricamento dinamico delle librerie a cui il tuo programma non era direttamente collegato. Tuttavia, tecnicamente sono al di fuori del linguaggio C ++ di base.


Tutto ? Anche quelli in dll che vengono caricati esplicitamente dopo main?
James Kanze

Ebbene, il C ++ non definisce tecnicamente le librerie caricate dinamicamente, quindi all'interno del C ++ puro, la mia affermazione è corretta. Quindi, ombreggia "Tutto, tranne che per gli inizializzatori e gli oggetti con ambito di file contenuti nelle DLL / DSO caricati dopo aver raggiunto main". In questo caso, mainè vuoto, quindi quelle DLL / DSO dovrebbero essere caricate dai distruttori, il che è dannatamente perverso. Ma, trattandosi di informatica, suppongo che dovremmo stare attenti a parole come "tutti".
Joe Z

Ho aggiunto un avvertimento su "tutto" alla mia risposta sopra e ho anche aggiunto una nota sui medici.
Joe Z

Sì, ma si spera che arriverà. Le versioni precedenti a C ++ 11 contenevano alcune formule di donnola intese a consentire le DLL, ma che in pratica significava solo che tecnicamente, la garanzia non era sempre presente, anche se era in tutte le implementazioni effettive, e molto codice dipendeva da essa. C ++ 11 ha risolto questo, almeno.
James Kanze
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.