Como é possível declarar nada dentro de main () em C ++ e ainda ter um aplicativo funcionando após a compilação?

86

Em uma entrevista, fui confrontado com uma pergunta como esta:

Seu amigo deu a você um único arquivo de código-fonte que imprime os números de Fibonacci no console. Observe que o bloco main () está vazio e não possui nenhuma instrução dentro dele.

Explique como isso é possível (dica: instância global!)

Eu realmente quero saber sobre isso, como uma coisa dessas pode ser possível!

Rika
fonte
26
Veja a dica!
R. Martinho Fernandes
14
Porque é algo que 1) eu não tinha ouvido falar, 2) é uma trivialidade útil porque as pessoas perguntam em entrevistas, 3) uma aplicação interessante da linguagem para saber para que 4) eu possa reconhecer e esfaquear qualquer pessoa no rosto com uma faca enferrujada se eu os vir realmente usando no código de produção
OmnipotentEntity
4
Um programador C ++ profissional e competente saberá a resposta a essa pergunta. Se o objetivo desta pergunta da entrevista é determinar se a pessoa que está sendo entrevistada é um programador C ++ profissional e competente, então a pergunta não deve dar a resposta.
John Dibling
1
Em um cenário de entrevista, uma alternativa seria ter a lógica dentro de qualquer função no código e registrar a saída usando assertou #pragma messageetc. Isso redirecionará a saída para o console durante a compilação. O programa pode nunca ser compilado totalmente, mas esta é uma maneira divertida de mostrar seu pensamento "out-of-the-box" durante a entrevista. Isso satisfaz a questão citada, pois NÃO menciona nada sobre a geração de binários; em vez disso, ele apenas fala sobre um arquivo C que pode exibir "coisas" no console. ;-)
TheCodeArtist
1
Foi uma entrevista para o IOCC ? :-) Ok, eu admito que faço isso frequentemente para inicializar minhas fábricas ou executar alguns códigos de teste. A propósito, ' arquivo de código-fonte único ' também é uma dica de que o entry-pint (principal por padrão) não é substituído pelo linker.
Valentin Heinitz

Respostas:

127

É mais provável que seja implementado como (ou uma variante dele):

 void print_fibs() 
 {
       //implementation
 }

 int ignore = (print_fibs(), 0);

 int main() {}

Neste código, a variável global ignoredeve ser inicializada antes de entrar na main()função. Agora, para inicializar o global, print_fibs()precisa ser executado onde você pode fazer qualquer coisa - neste caso, computar os números de fibonacci e imprimi-los! Algo semelhante que mostrei na seguinte pergunta (que eu havia feito há muito tempo):

Observe que esse código não é seguro e deve ser evitado em geral. Por exemplo, o std::coutobjeto não pode ser inicializado quando print_fibs()é executado, se sim, o que std::coutfaria na função? No entanto, se em outras circunstâncias, ele não depender dessa ordem de inicialização, então é seguro chamar as funções de inicialização (que é uma prática comum em C e C ++).

Nawaz
fonte
3
@Nawaz Provavelmente vale a pena citar as garantias exatas. Os objetos em uma unidade de tradução têm a garantia de serem inicializados em ordem. Os objetos de fluxo padrão têm a garantia de serem inicializados antes ou durante a primeira inicialização de um std::ios_base::Initobjeto. E <iostream>tem a garantia de se comportar "como se" contivesse uma instância de um std::ios_base_Initobjeto no escopo do namespace.
James Kanze
3
@ Steve314: Ele não retorna nada, por isso usei o operador vírgula, para garantir que o tipo de toda a expressão (print_fibs(), 0)seja int. Aqui está a demonstração online .
Nawaz
1
@Nawaz Uma alternativa para a função void e o operador vírgula seria retornar a boole a variável bool fibsPrinted. Isso provavelmente é um pouco mais limpo se a função só funcionar aqui. (Mas a diferença provavelmente não é suficiente para se preocupar.)
James Kanze
1
1, Fale sobre incrível. Tive que entrar no stackoverflow apenas para votar positivamente nesta pergunta e nesta resposta.
Ponto fixo de
1
@Nawaz Não tenho certeza de qual é o seu ponto. A definição de std::coutestá em algum lugar da biblioteca. Mas, como já indiquei, o padrão exige que ele seja inicializado antes que o primeiro construtor de um std::ios_base::Initobjeto termine, e exige que a inclusão <iostream>se comporte como se um std::ios_base::Initobjeto fosse definido no escopo do namespace. Se a unidade de tradução incluir <iostream>antes da definição do objeto sendo inicializado, std::couté garantido que será construído.
James Kanze
18

Espero que isto ajude

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

int main()
{
}

Assim que uma variável global da classe é declarada, o construtor é chamado e aí você adiciona a lógica para imprimir a série de Fibonacci.

Saksham
fonte
9

Sim, é possível. Você precisa declarar uma instância global de um objeto que calcula os números de Fibonacci no construtor do objeto.

Sr. Cerveja
fonte
6
Você precisa declarar uma instância global de um objeto cujo inicializador calcula os números de Fibonacci.
James Kanze
4

Eu conheço alguns exemplos como esse que você conta. Uma maneira de obtê-lo é usando a metaprogramação de modelo. Usando-o, você pode mover algum processo de computação para a compilação.

Aqui você pode obter um exemplo com os números de Fibonacci

Se você usá-lo em um construtor de classe estática, poderá escrever os números sem precisar escrever nenhum código na função principal.

Espero que ajude você.

superarce
fonte
3

Coisas podem acontecer durante a inicialização de variáveis ​​globais / estáticas. O código será acionado no início do aplicativo.

log0
fonte
3

Todos os construtores [*] para objetos de escopo de arquivo são chamados antes de alcançar main, assim como todas as expressões de inicializador para variáveis ​​de escopo de arquivo não-objeto.

Editar: Além disso, todos os destruidores [*] para todos os objetos de escopo de arquivo são chamados na ordem reversa de construção após as mainsaídas. Você poderia, teoricamente, colocar seu programa fibonacci no destruidor de um objeto.

[*] Observe que 'all' ignora o comportamento de carregar e descarregar dinamicamente as bibliotecas às quais seu programa não foi diretamente vinculado. Esses, tecnicamente, estão fora da linguagem C ++ base.

Joe Z
fonte
Tudo ? Mesmo aqueles em dlls que são carregados explicitamente depois main?
James Kanze
Bem, C ++ não define tecnicamente bibliotecas carregadas dinamicamente, portanto, dentro de C ++ puro, minha declaração está correta. Portanto, sombreie "Tudo, salve para inicializadores e objetos de escopo de arquivo contidos em DLLs / DSOs carregados após atingir o principal." Nesse caso, mainestá vazio, então essas DLLs / DSOs teriam que ser carregadas por destruidores, o que é muito perverso. Mas, sendo isso ciência da computação, acho que devemos ter cuidado com palavras como "todos".
Joe Z
Acrescentei uma advertência sobre 'todos' à minha resposta acima e também uma nota sobre dtors.
Joe Z
Sim, mas espero que isso aconteça. O pré C ++ 11 continha algumas palavras evasivas destinadas a permitir DLLs, mas que na prática apenas significava que, tecnicamente, a garantia nem sempre existia, mesmo que estivesse em todas as implementações reais, e muito código dependesse disso. C ++ 11 corrigiu isso, pelo menos.
James Kanze