Por que as funções precisam ser declaradas antes de serem usadas?

Ao ler algumas respostas para essa pergunta , comecei a me perguntar por que o compilador realmente precisa saber sobre uma function quando a encontra pela primeira vez. Não seria simples adicionar um passe extra ao analisar uma unidade de compilation que coleta todos os símbolos declarados, de modo que a ordem em que são declarados e usados ​​não tenha mais importância?

Pode-se argumentar que declarar funções antes de serem usadas certamente é um bom estilo, mas eu estou imaginando, existe alguma outra razão pela qual isso é obrigatório em C ++?

Editar – Um exemplo para ilustrar: suponha que você tenha funções definidas em linha em um arquivo de header. Essas duas funções chamam umas às outras (talvez uma passagem de tree recursiva, onde as camadas ímpares e pares da tree são tratadas de maneira diferente). A única maneira de resolver isso seria fazer uma declaração antecipada de uma das funções antes da outra.

Um exemplo mais comum (embora com classs, não funções) é o caso de classs com construtores e fábricas private . A fábrica precisa conhecer a class para criar instâncias, e a class precisa conhecer a fábrica para a declaração de friend .

Se este requisito é dos tempos antigos, por que não foi removido em algum momento? Não quebraria o código existente, seria?

Como você propõe resolver identificadores não declarados que são definidos em uma unidade de tradução diferente ?

C ++ não tem nenhum conceito de módulo, mas tem uma tradução separada como uma inheritance de C. Um compilador C ++ compilará cada unidade de tradução por si só, sem saber nada sobre outras unidades de tradução. (Exceto que a export quebrou isso, e é provavelmente por isso que, infelizmente, nunca decolou.)
Arquivos de header , que é onde você normalmente coloca declarações de identificadores que são definidos em outras unidades de tradução, na verdade são apenas uma forma muito desajeitada de colocar as mesmas declarações em diferentes unidades de tradução. Eles não farão com que o compilador saiba que existem outras unidades de tradução com identificadores definidos nelas.

Edite seus exemplos adicionais:
Com toda a inclusão textual em vez de um conceito de módulo adequado, a compilation já demora muito tempo para o C ++, então requerer outro passo de compilation (onde a compilation já é dividida em vários passos, nem todos podem ser otimizados e mesclados, o IIRC) iria piorar problema já ruim. E alterar isso provavelmente alteraria a resolução de sobrecarga em alguns cenários e, portanto, quebraria o código existente.

Observe que o C ++ requer um passe adicional para analisar as definições de class, pois as funções de membro definidas em linha na definição de class são analisadas como se estivessem definidas logo atrás da definição de class. No entanto, isso foi decidido quando o C with Classes foi criado, então não havia base de código existente para quebrar.

Porque C e C ++ são idiomas antigos . Os primeiros compiladores não tinham muita memory, então esses idiomas foram criados para que um compilador pudesse ler o arquivo de cima para baixo, sem ter que considerar o arquivo como um todo .

Historicamente, o C89 permite que você faça isso. A primeira vez que o compilador viu o uso de uma function e não tinha um protótipo pré-definido, “criou” um protótipo que combinava com o uso da function.

Quando o C ++ decidiu adicionar typechecking restrito ao compilador, foi decidido que os protótipos eram agora necessários. Além disso, o C ++ herdou a compilation de passagem única de C, portanto, não foi possível adicionar uma segunda passagem para resolver todos os símbolos.

Eu penso em duas razões:

  • Isso torna a análise fácil. Nenhum passe extra é necessário.
  • Também define o escopo ; Os símbolos / nomes estão disponíveis somente após a sua declaração. Significa, se eu declarar uma variável global int g_count; , o código posterior depois dessa linha pode usá-lo, mas não o código antes da linha! Mesmo argumento para funções globais.

Por exemplo, considere este código:

 void g(double) { cout << "void g(double)" << endl; } void f() { g(int());//this calls g(double) - because that is what is visible here } void g(int) { cout << "void g(int)" << endl; } int main() { f(); g(int());//calls g(int) - because that is what is the best match! } 

Saída:

void g (duplo)
void g (int)

Veja a saída em ideone: http://www.ideone.com/EsK4A

A principal razão será tornar o processo de compilation o mais eficiente possível. Se você adicionar um passe extra, estará adicionando tempo e armazenamento. Lembre-se que o C ++ foi desenvolvido antes do tempo dos Processadores Quad Core 🙂

A linguagem de programação C foi projetada para que o compilador pudesse ser implementado como um compilador de uma passagem . Em tal compilador, cada fase de compilation é executada apenas uma vez. Nesse compilador, você não pode referenciar uma entidade definida posteriormente no arquivo de origem.

Além disso, em C, o compilador só interpreta uma única unidade de compilation (geralmente um arquivo .c e todos os arquivos .h incluídos) por vez. Então você precisava de um mecanismo para referenciar uma function definida em outra unidade de compilation.

A decisão de permitir o compilador de uma passagem e de poder dividir um projeto em uma pequena unidade de compilation foi tomada porque, no momento em que a memory e a capacidade de processamento disponíveis eram realmente apertadas. E permitir a declaração antecipada poderia resolver facilmente o problema com um único recurso.

A linguagem C ++ foi derivada de C e herdou o recurso (como queria ser tão compatível com C quanto possível para facilitar a transição).

Eu acho que porque C é muito antigo e no momento C foi projetado compilation eficiente foi um problema porque os processadores eram muito mais lentos.

Como o C ++ é uma linguagem estática, o compilador precisa verificar se o tipo de valores é compatível com o tipo esperado nos parâmetros da function. É claro que, se você não conhece a assinatura da function, não pode fazer esse tipo de verificação, desafiando assim o propósito de um compilador estático. Mas, desde que você tenha um crachá de prata em C ++, eu acho que você já sabe disso.

As especificações da linguagem C ++ foram feitas corretamente porque o designer não queria forçar um compilador multi-pass, quando o hardware não era tão rápido quanto o disponível hoje. No final, acho que, se o C ++ fosse projetado hoje, essa imposição desapareceria, mas, então, teríamos outra linguagem :-).

Uma das maiores razões pelas quais isso foi obrigatório, mesmo em C99 (em comparação com C89, onde você poderia ter funções declaradas implicitamente) é que as declarações implícitas são muito propensas a erros. Considere o seguinte código:

Primeiro arquivo:

 #include  void doSomething(double x, double y) { printf("%g %g\n",x,y); } 

Segundo arquivo:

 int main() { doSomething(12345,67890); return 0; } 

Este programa é um programa * C89 sintaticamente válido. Você pode compilá-lo com o GCC usando este comando (assumindo que os arquivos de origem são denominados test.c e test0.c ):

 gcc -std=c89 -pedantic-errors test.c test0.c -o test 

Por que imprimir algo estranho (pelo menos no linux-x86 e linux-amd64)? Você consegue identificar o problema no código rapidamente? Agora tente replace c89 com c99 na linha de comando – e você será imediatamente notificado sobre o seu erro pelo compilador.

O mesmo com o C ++. Mas em C ++ existem outras razões importantes pelas quais as declarações de function são necessárias, elas são discutidas em outras respostas.

* Mas tem comportamento indefinido

Ainda assim, você pode usar uma function antes que ela seja declarada algumas vezes (para ser estrito no texto: “antes” é sobre a ordem na qual a fonte do programa é lida) – dentro de uma class !:

 class A { public: static void foo(void) { bar(); } private: static void bar(void) { return; } }; int main() { A::foo(); return 0; } 

(Alterar a class para um namespace não funciona, por meus testes.)

Isso é provavelmente porque o compilador realmente coloca as definições de function de membro de dentro da class logo após a declaração de class, como alguém apontou aqui nas respostas.

A mesma abordagem poderia ser aplicada a todo o arquivo de origem: primeiro, descarte tudo, menos a declaração, e depois manipule tudo o que foi adiado. (Um compilador de duas passagens ou memory grande o suficiente para conter o código-fonte adiado.)

Haha! Então, eles achavam que um arquivo fonte inteiro seria grande demais para conter a memory, mas uma única class com definições de function não permitiria: eles podem permitir que uma class inteira fique na memory e espere até que a declaração seja filtrada ( ou faça uma segunda passagem para o código fonte das classs!

Lembro-me com o Unix e o Linux, você tem Global e Local . Dentro de seu próprio ambiente, o local funciona para funções, mas não funciona para Global(system) . Você deve declarar a function Global .