Por que é necessária a * declaração * de dados e funções na linguagem C, quando a definição é escrita no final do código-fonte?

15

Considere o seguinte código "C":

#include<stdio.h>
main()
{   
  printf("func:%d",Func_i());   
}

Func_i()
{
  int i=3;
  return i;
}

Func_i()é definido no final do código-fonte e nenhuma declaração é fornecida antes do uso main(). No momento em que o compilador vê Func_i()em main(), ele sai do main()e descobre Func_i(). O compilador, de alguma forma, encontra o valor retornado por Func_i()e o entrega printf(). Eu também sei que o compilador não pode encontrar o tipo de retorno de Func_i(). Por padrão, assume (adivinha?) O tipo de retorno de Func_i()ser int. Ou seja, se o código tivesse float Func_i(), o compilador apresentaria o erro: Tipos conflitantes paraFunc_i() .

A partir da discussão acima, vemos que:

  1. O compilador pode encontrar o valor retornado por Func_i().

    • Se o compilador pode encontrar o valor retornado Func_i()saindo do main()e pesquisando o código-fonte, por que não consegue encontrar o tipo de Func_i (), mencionado explicitamente .
  2. O compilador deve saber que Func_i()é do tipo float - é por isso que gera o erro de tipos conflitantes.

  • Se o compilador sabe que Func_ié do tipo float, então por que ele ainda supõe Func_i()ser do tipo int e gera o erro de tipos conflitantes? Por que não faz com que Func_i()seja do tipo float?

Eu tenho a mesma dúvida com a declaração da variável . Considere o seguinte código "C":

#include<stdio.h>
main()
{
  /* [extern int Data_i;]--omitted the declaration */
  printf("func:%d and Var:%d",Func_i(),Data_i);
}

 Func_i()
{
  int i=3;
  return i;
}
int Data_i=4;

O compilador fornece o erro: 'Data_i' não declarado (primeiro uso nesta função).

  • Quando o compilador vê Func_i(), ele desce para o código-fonte para encontrar o valor retornado por Func_ (). Por que o compilador não pode fazer o mesmo para a variável Data_i?

Editar:

Não conheço os detalhes do trabalho interno do compilador, montador, processador etc. A idéia básica da minha pergunta é que, se eu contar (escrever) o valor de retorno da função no código-fonte, finalmente, após o uso dessa função, o idioma "C" permite que o computador encontre esse valor sem dar nenhum erro. Agora, por que o computador não consegue encontrar o tipo da mesma forma. Por que o tipo de Data_i não pode ser encontrado, pois o valor de retorno de Func_i () foi encontrado. Mesmo se eu usar a extern data-type identifier;instrução, não estou dizendo o valor a ser retornado por esse identificador (função / variável). Se o computador consegue encontrar esse valor, por que não consegue encontrar o tipo? Por que precisamos da declaração direta?

Obrigado.

user106313
fonte
7
O compilador não "encontra" o valor retornado por Func_i. isso é feito no tempo de execução.
James McLeod
26
Não diminuí o voto, mas a pergunta é baseada em alguns mal-entendidos sérios sobre como os compiladores funcionam, e suas respostas nos comentários sugerem que você ainda tem alguns obstáculos conceituais a serem superados.
James McLeod
4
Observe que o primeiro código de amostra não foi válido, conforme o código padrão nos últimos quinze anos; C99 fez a ausência de um tipo de retorno nas definições de função e a declaração implícita de Func_iinválido. Nunca houve uma regra para declarar implicitamente variáveis ​​indefinidas; portanto, o segundo fragmento sempre estava mal formado. (Sim, compiladores não aceitar a primeira amostra ainda porque era válida, se desleixado, sob C89 / C90.)
Jonathan Leffler
19
@ user31782: Conclusão da pergunta: Por que o idioma X requer / requer Y? Porque essa é a escolha que os designers fizeram. Parece que você está argumentando que os designers de uma das línguas de maior sucesso de todos os tempos deveriam ter feito escolhas diferentes décadas atrás, em vez de tentar entender essas escolhas no contexto em que foram feitas. A resposta para sua pergunta: Por que precisamos de uma declaração direta? foi dado: Como C usa um compilador de uma passagem. A resposta mais simples para a maioria das perguntas de acompanhamento é Porque não seria um compilador de uma passagem.
Mr.Mindor
4
@ user31782 Você realmente quer realmente ler o livro do dragão para entender como os compiladores e processadores realmente funcionam - é impossível destilar todo o conhecimento necessário em uma única resposta do SO (ou mesmo 100). Ótimo livro para qualquer pessoa interessada em compiladores.
Voo

Respostas:

26

Porque C é uma única passagem , estaticamente digitado , fracamente tipado , compilado linguagem.

  1. Passagem única significa que o compilador não olha para a frente para ver a definição de uma função ou variável. Como o compilador não olha para o futuro, a declaração de uma função deve vir antes do uso da função, caso contrário, o compilador não sabe qual é a sua assinatura de tipo. No entanto, a definição da função pode ser posterior no mesmo arquivo ou até mesmo em um arquivo diferente. Veja o ponto 4.

    A única exceção é o artefato histórico que funções e variáveis ​​não declaradas são do tipo "int". A prática moderna é evitar a digitação implícita, sempre declarando funções e variáveis ​​explicitamente.

  2. Tipo estaticamente significa que todas as informações de tipo são computadas em tempo de compilação. Essas informações são usadas para gerar código de máquina que é executado em tempo de execução. Não há conceito em C de digitação em tempo de execução. Uma vez que um int, sempre um int, uma vez um float, sempre um float. No entanto, esse fato é um pouco obscurecido pelo próximo ponto.

  3. Tipo fraco significa que o compilador C gera automaticamente código para converter entre tipos numéricos sem exigir que o programador especifique explicitamente as operações de conversão. Por causa da digitação estática, a mesma conversão sempre será realizada da mesma maneira sempre no programa. Se um valor flutuante é convertido em um valor int em um determinado ponto no código, um valor flutuante sempre será convertido em um valor int nesse ponto no código. Isso não pode ser alterado no tempo de execução. O valor em si pode mudar de uma execução do programa para a seguinte, é claro, e as instruções condicionais podem alterar quais seções do código são executadas em que ordem, mas uma única seção do código sem chamadas de função ou condicionais sempre executará exatamente o mesmas operações sempre que for executada.

  4. Compilado significa que o processo de analisar o código-fonte legível por humanos e transformá-lo em instruções legíveis por máquina é totalmente realizado antes da execução do programa. Quando o compilador está compilando uma função, ele não tem conhecimento do que encontrará mais adiante em um determinado arquivo de origem. No entanto, depois que a compilação (e montagem, vinculação, etc.) for concluída, cada função no executável finalizado conterá ponteiros numéricos para as funções que ele chamará quando for executado. É por isso que main () pode chamar uma função mais abaixo no arquivo de origem. Quando o main () for executado, ele conterá um ponteiro para o endereço de Func_i ().

    O código da máquina é muito, muito específico. O código para adicionar dois números inteiros (3 + 2) é diferente do código para adicionar dois números flutuantes (3.0 + 2.0). Eles são diferentes de adicionar um int a um float (3 + 2.0) e assim por diante. O compilador determina para cada ponto de uma função qual operação exata precisa ser executada nesse ponto e gera código que executa essa operação exata. Feito isso, ele não pode ser alterado sem recompilar a função.

Juntando todos esses conceitos, a razão pela qual main () não pode "ver" mais abaixo para determinar o tipo de Func_i () é que a análise de tipo ocorre no início do processo de compilação. Nesse ponto, apenas a parte do arquivo de origem até a definição de main () foi lida e analisada, e a definição de Func_i () ainda não é conhecida pelo compilador.

O motivo pelo qual main () pode "ver" onde Func_i () deve chamá- lo é que a chamada acontece no tempo de execução, depois que a compilação já resolveu todos os nomes e tipos de todos os identificadores, o assembly já converteu todos os funções ao código da máquina e a vinculação já inseriu o endereço correto de cada função em cada local em que é chamada.

Evidentemente, deixei de fora a maioria dos detalhes sangrentos. O processo real é muito, muito mais complicado. Espero ter fornecido uma visão geral de alto nível o suficiente para responder às suas perguntas.

Além disso, lembre-se de que o que escrevi acima se aplica especificamente a C.

Em outros idiomas, o compilador pode fazer várias passagens pelo código-fonte e, assim, o compilador pode pegar a definição de Func_i () sem que seja pré-declarado.

Em outros idiomas, funções e / ou variáveis ​​podem ser digitadas dinamicamente, para que uma única variável possa reter ou uma única função possa ser passada ou retornada, um número inteiro, um número flutuante, uma sequência, uma matriz ou um objeto em momentos diferentes.

Em outros idiomas, a digitação pode ser mais forte, exigindo que a conversão do ponto flutuante para o número inteiro seja especificada explicitamente. Em outros idiomas, a digitação pode ser mais fraca, permitindo que a conversão da string "3.0" para o float 3.0 no número inteiro 3 seja realizada automaticamente.

E em outros idiomas, o código pode ser interpretado uma linha de cada vez, ou compilado em código de bytes e, em seguida, interpretado, ou compilado na hora certa, ou submetido a uma ampla variedade de outros esquemas de execução.

Clement Cherlin
fonte
1
Obrigado por uma resposta tudo em um. A resposta de você e Nikie é o que eu queria saber. Por exemplo Func_()+1: aqui, no momento da compilação, o compilador precisa saber o tipo de Func_i(), para gerar o código de máquina apropriado. Talvez não seja possível para o assembly manipular Func_()+1chamando o tipo em tempo de execução, ou é possível, mas fazê-lo tornará o programa lento no tempo de execução. Eu acho que é o suficiente para mim por enquanto.
user106313
1
Detalhes importantes das funções implicitamente declaradas de C: Elas são assumidas como do tipo int func(...)... ou seja, recebem uma lista de argumentos variados. Isso significa que, se você definir uma função como int putc(char)mas esquecer de declará-la, ela será chamada como int putc(int)(porque char passado por uma lista de argumentos variados é promovido para int). Portanto, enquanto o exemplo do OP funcionou porque sua assinatura correspondia à declaração implícita, é compreensível que esse comportamento tenha sido desencorajado (e os avisos apropriados foram adicionados).
Uliwitness
37

Uma restrição de design da linguagem C era que ela deveria ser compilada por um compilador de passagem única, o que a torna adequada para sistemas com muita restrição de memória. Portanto, o compilador sabe a qualquer momento apenas sobre as coisas mencionadas anteriormente. O compilador não pode pular adiante na fonte para encontrar uma declaração de função e depois voltar para compilar uma chamada para essa função. Portanto, todos os símbolos devem ser declarados antes de serem utilizados. Você pode pré-declarar uma função como

int Func_i();

na parte superior ou em um arquivo de cabeçalho para ajudar o compilador.

Nos seus exemplos, você usa dois recursos duvidosos da linguagem C que devem ser evitados:

  1. Se uma função é usada antes de ser declarada corretamente, é usada como uma "declaração implícita". O compilador usa o contexto imediato para descobrir a assinatura da função. O compilador não varrerá o restante do código para descobrir qual é a declaração real.

  2. Se algo for declarado sem um tipo, o tipo será considerado int. É o caso, por exemplo, de variáveis ​​estáticas ou tipos de retorno de função.

Então printf("func:%d",Func_i()), temos uma declaração implícita int Func_i(). Quando o compilador atinge a definição de função Func_i() { ... }, isso é compatível com o tipo Mas se você escreveu float Func_i() { ... }neste momento, você tem a implicação declarada int Func_i()e explicitamente declarada float Func_i(). Como as duas declarações não coincidem, o compilador apresenta um erro.

Limpando alguns equívocos

  • O compilador não encontra o valor retornado por Func_i. A ausência de um tipo explícito significa que o tipo de retorno é intpor padrão. Mesmo se você fizer isso:

    Func_i() {
        float f = 42.3;
        return f;
    }

    então o tipo será int Func_i()e o valor de retorno será truncado silenciosamente!

  • O compilador finalmente conhece o tipo real de Func_i, mas não conhece o tipo real durante a declaração implícita. Somente quando mais tarde alcança a declaração real, é possível descobrir se o tipo declarado implicitamente estava correto. Mas nesse ponto, o assembly da chamada de função já pode ter sido gravado e não pode ser alterado no modelo de compilação C.

amon
fonte
3
@ user31782: a ordem do código é importante no tempo de compilação, mas não no tempo de execução. O compilador está fora de cena quando o programa é executado. No tempo de execução, a função será montada e vinculada, seu endereço será resolvido e bloqueado no espaço reservado para o endereço da chamada. (É um pouco mais complexo que isso, mas essa é a idéia básica.) O processador pode se ramificar para frente ou para trás.
Blrfl
20
@ user31782: O compilador não imprime o valor. Seu compilador não executa o programa !!
Lightness Races com Monica
1
@LightnessRacesinOrbit Eu sei disso. Por engano, escrevi o compilador no meu comentário acima porque esqueci o processador de nomes .
user106313
3
O @Carcigenicate C foi fortemente influenciado pela linguagem B, que tinha apenas um único tipo: um tipo numérico integral com largura de palavra que também poderia ser usado para ponteiros. C originalmente copiou esse comportamento, mas agora é completamente proibido desde o padrão C99. Unitfaz um bom tipo padrão do ponto de vista da teoria dos tipos, mas falha nos aspectos práticos da programação próxima aos sistemas metálicos para os quais B e C foram projetados.
amon
2
@ user31782: O compilador deve conhecer o tipo da variável para gerar a montagem correta para o processador. Quando o compilador encontra o implícito Func_i(), ele imediatamente gera e salva o código para o processador saltar para outro local, receber um número inteiro e continuar. Quando o compilador encontra a Func_idefinição posteriormente , ele garante que as assinaturas correspondam e, se o fizerem, coloca o assembly Func_i()nesse endereço e diz para ele retornar algum número inteiro. Quando você executar o programa, o processador, em seguida, segue essas instruções com o valor 3.
Mooing Duck
10

Primeiro, seus programas são válidos para o padrão C90, mas não para os seguintes. int implícito (permitindo declarar uma função sem fornecer seu tipo de retorno) e declaração implícita de funções (permitindo usar uma função sem declará-la) não são mais válidas.

Segundo, isso não funciona como você pensa.

  1. O tipo de resultado é opcional no C90, não fornecendo um significa um intresultado. Isso também é válido para a declaração de variáveis ​​(mas você precisa fornecer uma classe de armazenamento staticou extern).

  2. O que o compilador faz ao ver a Func_ichamada é sem uma declaração anterior, está assumindo que existe uma declaração

    extern int Func_i();

    ele não procura mais no código para ver com que eficácia Func_ié declarada. Se Func_inão fosse declarado ou definido, o compilador não mudaria seu comportamento ao compilar main. A declaração implícita é apenas para função, não há para variável.

    Observe que a lista de parâmetros vazia na declaração não significa que a função não aceita parâmetros (é necessário especificar (void)isso), significa que o compilador não precisa verificar os tipos dos parâmetros e terá o mesmo conversões implícitas que são aplicadas a argumentos passados ​​para funções variadas.

AProgrammer
fonte
Se o compilador pode encontrar o valor retornado por Func_i () saindo do main () e pesquisando o código-fonte, por que não consegue encontrar o tipo de Func_i (), mencionado explicitamente.
user106313
1
@ user31782 Se não houve declaração anterior de Func_i, ao ver que Func_i é usado em uma expressão de chamada, se comporta como se houvesse uma extern int Func_i(). Não parece em lugar algum.
AProgrammer
1
@ user31782, o compilador não pula para lugar nenhum. Ele emitirá código para chamar essa função; o valor retornado será determinado no tempo de execução. Bem, no caso de uma função tão simples que está presente na mesma unidade de compilação, a fase de otimização pode incorporar a função, mas não é algo que você deva pensar ao considerar as regras da linguagem, é uma otimização.
AProgrammer
10
@ user31782, você tem sérios mal-entendidos sobre como os programas funcionam. Tão sério que não acho que p.se seja um bom lugar para corrigi-los (talvez o bate-papo, mas não vou tentar fazê-lo).
AProgramador 23/12
1
@ user31782: Escrever um pequeno trecho e compilá-lo com -S(se você estiver usando gcc) permitirá que você veja o código de montagem gerado pelo compilador. Em seguida, você pode ter uma idéia de como os valores de retorno são tratados no tempo de execução (normalmente usando um registro do processador ou algum espaço na pilha do programa).
Giorgio
7

Você escreveu em um comentário:

A execução é feita linha por linha. A única maneira de encontrar o valor retornado por Func_i () é pular da janela principal

Isso é um equívoco: a execução não é realizada linha por linha. A compilação é feita linha por linha, e a resolução de nomes é feita durante a compilação, e só resolve nomes, não retorna valores.

Um modelo conceitual útil é o seguinte: Quando o compilador lê a linha:

  printf("func:%d",Func_i());

emite código equivalente a:

  1. call "function #2" and put the return value on the stack
  2. put the constant string "func:%d" on the stack
  3. call "function #1"

O compilador também faz uma anotação em alguma tabela interna que ainda function #2é uma função declarada denominada Func_i, que pega um número não especificado de argumentos e retorna um int (o padrão).

Mais tarde, quando ele analisa isso:

 int Func_i() { ...

o compilador Func_iconsulta a tabela mencionada acima e verifica se os parâmetros e o tipo de retorno correspondem. Caso contrário, para com uma mensagem de erro. Se o fizerem, adiciona o endereço atual à tabela de funções internas e segue para a próxima linha.

Portanto, o compilador não "procurou" Func_iquando analisou a primeira referência. Simplesmente anotou em alguma tabela e continuou analisando a próxima linha. E no final do arquivo, ele possui um arquivo de objeto e uma lista de endereços de salto.

Posteriormente, o vinculador pega tudo isso e substitui todos os ponteiros da "função # 2" pelo endereço de salto real, para emitir algo como:

  call 0x0001215 and put the result on the stack
  put constant ... on the stack
  call ...
...
[at offset 0x0001215 in the file, compiled result of Func_i]:
  put 3 on the stack
  return top of the stack

Muito mais tarde, quando o arquivo executável é executado, o endereço de salto já está resolvido e o computador pode simplesmente pular para o endereço 0x1215. Nenhuma pesquisa de nome é necessária.

Isenção de responsabilidade : como eu disse, esse é um modelo conceitual e o mundo real é mais complicado. Compiladores e vinculadores fazem todos os tipos de otimizações malucas hoje. Eles podem até "pular para cima" para procurar Func_i, embora eu duvide. Mas a linguagem C é definida de uma maneira que você pode escrever um compilador super simples como esse. Então, na maioria das vezes, é um modelo muito útil.

nikie
fonte
Obrigado pela sua resposta. O compilador não pode emitir o código:1. call "function #2", put the return-type onto the stack and put the return value on the stack?
user106313
1
(Cont.) Também: E se você escreveu printf(..., Func_i()+1);- o compilador precisa saber o tipo de Func_i, para poder decidir se deve emitir uma add integerou uma add floatinstrução. Você pode encontrar alguns casos especiais em que o compilador pode continuar sem as informações de tipo, mas o compilador precisa funcionar para todos os casos.
Nikie
4
@ user31782: As instruções da máquina, em regra, são muito simples: Adicione dois registradores inteiros de 32 bits. Carregue um endereço de memória em um registro inteiro de 16 bits. Ir para um endereço. Além disso, não existem tipos : você pode carregar felizmente um local de memória que represente um número flutuante de 32 bits em um registro inteiro de 32 bits e fazer alguma aritmética com ele. (Isso raramente faz sentido.) Portanto, não, você não pode emitir código de máquina assim diretamente. Você pode escrever um compilador que faça tudo isso com verificações de tempo de execução e dados de tipo extra na pilha. Mas não seria um compilador C.
Nikie
1
@ user31782: Depende, IIRC. floatvalores podem viver em um registro FPU - então não haveria nenhuma instrução. O compilador apenas controla qual valor é armazenado em qual registro durante a compilação e emite coisas como "adicione constante 1 ao registro FP X". Ou poderia permanecer na pilha, se não houver registros livres. Em seguida, haveria "aumentar o ponteiro da pilha em 4" e o valor seria "referenciado" como algo como "ponteiro da pilha - 4". Mas todas essas coisas só funcionam se os tamanhos de todas as variáveis ​​(antes e depois) na pilha forem conhecidos em tempo de compilação.
Nikie
1
De toda a discussão que cheguei a esse entendimento: Para o compilador criar um código de montagem plausível para qualquer instrução que inclua Func_i()ou / e Data_i, ele deve determinar seus tipos; não é possível na linguagem assembly fazer uma chamada para o tipo de dados. Eu preciso estudar as coisas em detalhes para ter certeza.
user106313
5

C e várias outras linguagens que exigem declarações foram projetadas em uma época em que o tempo e a memória do processador eram caros. O desenvolvimento de C e Unix andou de mãos dadas por algum tempo, e este último não tinha memória virtual até o 3BSD aparecer em 1979. Sem espaço extra para trabalhar, os compiladores tendiam a ser casos de passagem única , porque não tinham. requer a capacidade de manter alguma representação de todo o arquivo na memória de uma só vez.

Compiladores de passagem única são, como nós, sobrecarregados com a incapacidade de ver o futuro. Isso significa que as únicas coisas que eles podem ter certeza são o que disseram explicitamente antes da compilação da linha de código. É claro para qualquer um de nós que Func_i()é declarado posteriormente no arquivo de origem, mas o compilador, que opera em um pequeno pedaço de código de cada vez, não tem idéia de que está chegando.

No início de C (AT&T, K&R, C89), o uso de uma função foo()antes da declaração resultou em uma declaração de fato ou implícita de int foo(). Seu exemplo funciona quando Func_i()é declarado intporque corresponde ao que o compilador declarou em seu nome. Mudá-lo para qualquer outro tipo resultará em conflito porque não corresponde mais ao que o compilador escolheu na ausência de uma declaração explícita. Esse comportamento foi removido no C99, onde o uso de uma função não declarada se tornou um erro.

E os tipos de retorno?

A convenção de chamada para código de objeto na maioria dos ambientes requer conhecer apenas o endereço da função que está sendo chamada, o que é relativamente fácil de lidar com compiladores e vinculadores. A execução pula para o início da função e volta quando ela retorna. Qualquer outra coisa, principalmente arranjos de transmissão de argumentos e um valor de retorno, é determinada inteiramente pelo chamador e chamado em um arranjo chamado de convenção de chamada . Desde que ambos compartilhem o mesmo conjunto de convenções, torna-se possível para um programa chamar funções em outros arquivos de objeto, independentemente de terem sido compilados em qualquer idioma que compartilhe essas convenções. (Na computação científica, você encontra muitas chamadas em C do FORTRAN e vice-versa, e a capacidade de fazer isso vem de ter uma convenção de chamadas.)

Uma outra característica do C inicial era que os protótipos como os conhecemos agora não existiam. Você pode declarar o tipo de retorno de uma função (por exemplo, int foo()), mas não seus argumentos (por exemplo, int foo(int bar)não era uma opção). Isso existia porque, conforme descrito acima, o programa sempre mantinha uma convenção de chamada que poderia ser determinada pelos argumentos. Se você chamou uma função com o tipo errado de argumentos, era uma situação de entrada e saída de lixo.

Como o código do objeto tem a noção de um retorno, mas não um tipo de retorno, um compilador precisa conhecer o tipo de retorno para lidar com o valor retornado. Quando você está executando as instruções da máquina, são apenas bits e o processador não se importa se a memória em que você está tentando comparar um doublerealmente tem um int. Ele apenas faz o que você pede e, se você o quebrar, possui as duas peças.

Considere estes bits de código:

double foo();         double foo();
double x;             int x;
x = foo();            x = foo();

O código à esquerda é compilado em uma chamada para, foo()seguido pela cópia do resultado fornecido pela convenção de chamada / retorno para onde quer que xesteja armazenado. Esse é o caso fácil.

O código à direita mostra uma conversão de tipo e é por isso que os compiladores precisam conhecer o tipo de retorno de uma função. Números de ponto flutuante não podem ser despejados na memória, onde outros códigos esperam ver um intporque não há conversão mágica que ocorre. Se o resultado final precisar ser um número inteiro, é necessário que haja instruções que orientem o processador para fazer a conversão antes do armazenamento. Sem saber o tipo de retorno foo()antecipadamente, o compilador não teria idéia de que o código de conversão é necessário.

Os compiladores de múltiplas passagens permitem todo tipo de coisa, uma das quais é a capacidade de declarar variáveis, funções e métodos após a primeira utilização. Isso significa que quando o compilador começa a compilar o código, ele já viu o futuro e sabe o que fazer. Java, por exemplo, exige múltiplas passagens em virtude do fato de sua sintaxe permitir a declaração após o uso.

Blrfl
fonte
Obrigado pela sua resposta (+1). Não conheço os detalhes do trabalho interno do compilador, montador, processador etc. A idéia básica da minha pergunta é que, se eu contar (escrever) o valor de retorno da função no código-fonte, finalmente, após o uso dessa função, o idioma permite que o computador encontre esse valor sem dar nenhum erro. Agora, por que o computador não consegue encontrar o tipo da mesma forma. Por que o tipo de Data_i não pode ser encontrado como Func_i()o valor de retorno foi encontrado.
user106313
Eu ainda não estou satisfeito. double foo(); int x; x = foo();simplesmente dá o erro. Eu sei que não podemos fazer isso. Minha pergunta é que, na chamada de função, o processador encontra apenas o valor de retorno; por que não pode também encontrar o tipo de retorno também?
user106313
1
@ user31782: Não deveria. Existe um protótipo para foo()que o compilador saiba o que fazer com ele.
Blrfl
2
@ user31782: Os processadores não têm noção do tipo de retorno.
Blrfl
1
@ user31782 Para a questão do tempo de compilação: É possível escrever uma linguagem na qual toda essa análise de tipo possa ser feita em tempo de compilação. C não é esse idioma. O compilador C não pode fazê-lo porque não foi projetado para fazê-lo. Poderia ter sido projetado de maneira diferente? Claro, mas seria necessário muito mais poder de processamento e memória para isso. Bottom line é que não era. Foi projetado de uma maneira que os computadores da época eram mais capazes de lidar.
Mr.Mindor