¿Por qué es necesaria la * declaración * de datos y funciones en lenguaje C, cuando la definición se escribe al final del código fuente?

15

Considere el siguiente código "C":

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

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

Func_i()se define al final del código fuente y no se proporciona ninguna declaración antes de su uso en main(). En el mismo momento en que el compilador ve Func_i()en main(), sale de la main()y se entera Func_i(). El compilador de alguna manera encuentra el valor devuelto por Func_i()y se lo da printf(). También sé que el compilador no puede encontrar el tipo de retorno de Func_i(). Por defecto, toma (¿adivina?) El tipo de retorno de Func_i()ser int. Es decir, si el código tuviera float Func_i()entonces el compilador daría el error: Tipos conflictivos paraFunc_i() .

De la discusión anterior vemos que:

  1. El compilador puede encontrar el valor devuelto por Func_i().

    • Si el compilador puede encontrar el valor devuelto al Func_i()salir main()y buscar el código fuente, entonces ¿por qué no puede encontrar el tipo de Func_i (), que se menciona explícitamente ?
  2. El compilador debe saber que Func_i()es de tipo flotante, es por eso que da el error de tipos en conflicto.

  • Si el compilador sabe que Func_ies de tipo flotante, ¿por qué sigue suponiendo Func_i()que es de tipo int y da el error de tipos en conflicto? ¿Por qué no hace forzosamente Func_i()ser de tipo flotante?

Tengo la misma duda con la declaración de variable . Considere el siguiente 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;

El compilador da el error: 'Data_i' no declarado (primer uso en esta función).

  • Cuando el compilador ve Func_i(), baja al código fuente para encontrar el valor devuelto por Func_ (). ¿Por qué el compilador no puede hacer lo mismo para la variable Data_i?

Editar:

No conozco los detalles del funcionamiento interno del compilador, ensamblador, procesador, etc. La idea básica de mi pregunta es que si por fin digo (escribo) el valor de retorno de la función en el código fuente, después del uso de esa función, entonces el lenguaje "C" permite que la computadora encuentre ese valor sin dar ningún error. Ahora, ¿por qué la computadora no puede encontrar el tipo de manera similar? ¿Por qué no se puede encontrar el tipo de Data_i como se encontró el valor de retorno de Func_i ()? Incluso si uso la extern data-type identifier;declaración, no estoy diciendo que el valor sea devuelto por ese identificador (función / variable). Si la computadora puede encontrar ese valor, ¿por qué no puede encontrar el tipo? ¿Por qué necesitamos la declaración adelantada?

Gracias.

usuario106313
fuente
77
El compilador no "encuentra" el valor devuelto por Func_i. Esto se hace en tiempo de ejecución.
James McLeod
26
No voté en contra, pero la pregunta se basa en algunos malentendidos serios sobre cómo funcionan los compiladores, y sus respuestas en los comentarios sugieren que aún tiene algunos obstáculos conceptuales que superar.
James McLeod
44
Tenga en cuenta que el primer código de muestra no ha sido válido, código conforme a los estándares durante los últimos quince años; C99 hizo la ausencia de un tipo de retorno en las definiciones de función y la declaración implícita de Func_iinválido. Nunca hubo una regla para declarar implícitamente variables indefinidas, por lo que el segundo fragmento siempre estaba mal formado. (Sí, los compiladores todavía aceptan la primera muestra porque era válida, si era descuidada, según C89 / C90.)
Jonathan Leffler el
19
@ user31782: Conclusión de la pregunta: ¿por qué el lenguaje X requiere / requiere Y? Porque esa es la elección que hicieron los diseñadores. Parece que está argumentando que los diseñadores de uno de los lenguajes más exitosos de la historia deberían haber tomado decisiones diferentes hace décadas en lugar de tratar de entender esas elecciones en el contexto en que se hicieron. La respuesta a su pregunta: ¿Por qué necesitamos una declaración anticipada? se le ha dado: porque C usa un compilador de una pasada. La respuesta más simple a la mayoría de sus preguntas de seguimiento es Porque entonces no sería un compilador de una pasada.
Mr.Mindor
44
@ user31782 Realmente, realmente quieres leer el libro del dragón para entender cómo funcionan realmente los compiladores y procesadores; es simplemente imposible resumir todo el conocimiento requerido en una sola respuesta SO (o incluso 100). Gran libro para cualquier persona interesada en compiladores.
Voo

Respuestas:

26

Debido a que C es un solo pase , tipos estáticos- , débilmente tipificado , compilado lenguaje.

  1. Single-pass significa que el compilador no mira hacia adelante para ver la definición de una función o variable. Como el compilador no mira hacia adelante, la declaración de una función debe venir antes del uso de la función; de lo contrario, el compilador no sabe cuál es su firma de tipo. Sin embargo, la definición de la función puede estar más adelante en el mismo archivo, o incluso en un archivo completamente diferente. Ver punto 4.

    La única excepción es el artefacto histórico de que se supone que las funciones y variables no declaradas son del tipo "int". La práctica moderna es evitar la tipificación implícita declarando siempre funciones y variables explícitamente.

  2. Tipo estático significa que toda la información de tipo se calcula en tiempo de compilación. Esa información se utiliza para generar código de máquina que se ejecuta en tiempo de ejecución. No hay ningún concepto en C de mecanografía en tiempo de ejecución. Una vez int, siempre int, una vez flotante, siempre flotante. Sin embargo, ese hecho está algo oscurecido por el siguiente punto.

  3. Tipo débil significa que el compilador de C genera automáticamente código para convertir entre tipos numéricos sin requerir que el programador especifique explícitamente las operaciones de conversión. Debido al tipeo estático, la misma conversión siempre se realizará de la misma manera cada vez a través del programa. Si un valor flotante se convierte en un valor int en un punto dado en el código, un valor flotante siempre se convertirá en un valor int en ese punto en el código. Esto no se puede cambiar en tiempo de ejecución. El valor en sí mismo puede cambiar de una ejecución del programa a la siguiente, por supuesto, y las declaraciones condicionales pueden cambiar qué secciones del código se ejecutan en qué orden, pero una sola sección de código dada sin llamadas a funciones o condicionales siempre realizará exactamente mismas operaciones cada vez que se ejecuta.

  4. Compilado significa que el proceso de analizar el código fuente legible por humanos y transformarlo en instrucciones legibles por máquina se lleva a cabo antes de que se ejecute el programa. Cuando el compilador está compilando una función, no tiene conocimiento de lo que encontrará más abajo en un archivo fuente dado. Sin embargo, una vez que se ha completado la compilación (y ensamblaje, vinculación, etc.), cada función en el ejecutable finalizado contiene punteros numéricos a las funciones que llamará cuando se ejecute. Es por eso que main () puede llamar a una función más abajo en el archivo fuente. Para cuando se ejecute main (), contendrá un puntero a la dirección de Func_i ().

    El código de la máquina es muy, muy específico. El código para agregar dos enteros (3 + 2) es diferente del que se usa para agregar dos flotantes (3.0 + 2.0). Ambos son diferentes de agregar un int a un flotante (3 + 2.0), y así sucesivamente. El compilador determina para cada punto de una función qué operación exacta necesita llevarse a cabo en ese punto, y genera un código que lleva a cabo esa operación exacta. Una vez hecho esto, no se puede cambiar sin volver a compilar la función.

Al unir todos estos conceptos, la razón por la cual main () no puede "ver" más abajo para determinar el tipo de Func_i () es que el análisis de tipo ocurre al comienzo del proceso de compilación. En ese momento, solo se ha leído y analizado la parte del archivo fuente hasta la definición de main (), y el compilador aún no conoce la definición de Func_i ().

La razón por la que main () puede "ver" dónde Func_i () debe llamarlo es porque la llamada ocurre en tiempo de ejecución, después de que la compilación ya ha resuelto todos los nombres y tipos de todos los identificadores, el ensamblaje ya ha convertido todos los funciones al código de máquina, y la vinculación ya ha insertado la dirección correcta de cada función en cada lugar que se llama.

Por supuesto, he omitido la mayoría de los detalles sangrientos. El proceso real es mucho, mucho más complicado. Espero haber proporcionado suficiente información general de alto nivel para responder a sus preguntas.

Además, recuerde que lo que he escrito anteriormente se aplica específicamente a C.

En otros idiomas, el compilador puede realizar múltiples pases a través del código fuente, por lo que el compilador podría elegir la definición de Func_i () sin que esté previamente declarado.

En otros idiomas, las funciones y / o variables se pueden escribir dinámicamente, por lo que una sola variable podría contener, o se podría pasar o devolver una sola función, un entero, un flotante, una cadena, una matriz o un objeto en diferentes momentos.

En otros idiomas, la escritura puede ser más fuerte, lo que requiere que se especifique explícitamente la conversión de punto flotante a entero. En otros idiomas, la escritura puede ser más débil, permitiendo que la conversión de la cadena "3.0" al float 3.0 al entero 3 se realice automáticamente.

Y en otros idiomas, el código puede interpretarse una línea a la vez, o compilarse en código de bytes y luego interpretarse, o compilarse justo a tiempo, o pasar por una amplia variedad de otros esquemas de ejecución.

Clement Cherlin
fuente
1
Gracias por una respuesta todo en uno. Tu respuesta y la de Nikie es lo que quería saber. Por ejemplo Func_()+1: aquí en el tiempo de compilación el compilador tiene que saber el tipo de Func_i()fin de generar el código de máquina apropiada. Quizás o no sea posible que el ensamblado lo maneje Func_()+1llamando al tipo en tiempo de ejecución, o sea posible, pero hacerlo hará que el programa se ralentice en el tiempo de ejecución. Creo que es suficiente para mí por ahora.
user106313
1
Detalle importante de las funciones declaradas implícitamente de C: se supone que son de tipo int func(...)... es decir, toman una lista de argumentos variadic. Esto significa que si define una función como int putc(char)pero se olvida de declararla, en su lugar se llamará como int putc(int)(porque el char pasado a través de una lista de argumentos variados es promovido a int). Entonces, aunque el ejemplo del OP funcionó porque su firma coincidía con la declaración implícita, es comprensible por qué este comportamiento se desalentó (y se agregaron las advertencias apropiadas).
uliwitness
37

Una restricción de diseño del lenguaje C era que se suponía que debía ser compilado por un compilador de un solo paso, lo que lo hace adecuado para sistemas con limitaciones de memoria. Por lo tanto, el compilador solo conoce en cualquier momento las cosas que se mencionaron anteriormente. El compilador no puede saltar hacia adelante en la fuente para encontrar una declaración de función y luego volver a compilar una llamada a esa función. Por lo tanto, todos los símbolos deben declararse antes de ser utilizados. Puede pre-declarar una función como

int Func_i();

en la parte superior o en un archivo de encabezado para ayudar al compilador.

En sus ejemplos, utiliza dos características dudosas del lenguaje C que deben evitarse:

  1. Si una función se usa antes de que se declare correctamente, se usa como una "declaración implícita". El compilador utiliza el contexto inmediato para descubrir la firma de la función. El compilador no escaneará el resto del código para descubrir cuál es la declaración real.

  2. Si se declara algo sin un tipo, se considera que el tipo es int. Este es, por ejemplo, el caso de variables estáticas o tipos de retorno de funciones.

Entonces printf("func:%d",Func_i()), tenemos una declaración implícita int Func_i(). Cuando el compilador alcanza la definición de la función Func_i() { ... }, esto es compatible con el tipo. Pero si escribiste float Func_i() { ... }en este punto, tienes la implicidad declarada int Func_i()y la explícitamente declarada float Func_i(). Como las dos declaraciones no coinciden, el compilador le da un error.

Aclarando algunas ideas falsas

  • El compilador no encuentra el valor devuelto por Func_i. La ausencia de un tipo explícito significa que el tipo de retorno es intpor defecto. Incluso si haces esto:

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

    entonces el tipo será int Func_i(), ¡y el valor de retorno se truncará silenciosamente!

  • El compilador finalmente conoce el tipo real de Func_i, pero no conoce el tipo real durante la declaración implícita. Solo cuando más tarde alcanza la declaración real puede averiguar si el tipo declarado implícitamente era correcto. Pero en ese punto, el ensamblado para la llamada a la función ya podría haberse escrito y no se puede cambiar en el modelo de compilación C.

amon
fuente
3
@ user31782: el orden del código es importante en tiempo de compilación, pero no en tiempo de ejecución. El compilador está fuera de la imagen cuando se ejecuta el programa. En el tiempo de ejecución, la función se habrá ensamblado y vinculado, su dirección se habrá resuelto y pegado en el marcador de posición de la dirección de la llamada. (Es un poco más complejo que eso, pero esa es la idea básica). El procesador puede ramificarse hacia adelante o hacia atrás.
Blrfl
20
@ user31782: el compilador no imprime el valor. ¡Tu compilador no ejecuta el programa!
Lightness compite con Monica el
1
@LightnessRacesinOrbit Lo sé. Escribí por error el compilador en mi comentario anterior porque olvidé el procesador de nombres .
user106313
3
@Carcigenicate C estuvo fuertemente influenciado por el lenguaje B, que solo tenía un solo tipo: un tipo numérico integral de ancho de palabra que también podría usarse para punteros. C originalmente copió este comportamiento, pero ahora está completamente prohibido desde el estándar C99. Unithace un buen tipo predeterminado desde el punto de vista de la teoría de tipos, pero falla en los aspectos prácticos de la programación cercana a los sistemas metálicos para los que B y C fueron diseñados.
amon
2
@ user31782: el compilador debe conocer el tipo de variable para generar el ensamblaje correcto para el procesador. Cuando el compilador encuentra lo implícito Func_i(), genera y guarda inmediatamente el código para que el procesador salte a otra ubicación, luego reciba un número entero y luego continúe. Cuando el compilador luego encuentra la Func_idefinición, se asegura de que las firmas coincidan, y si lo hacen, coloca el ensamblado Func_i()en esa dirección y le dice que devuelva algún número entero. Cuando ejecuta el programa, el procesador sigue esas instrucciones con el valor 3.
Mooing Duck
10

Primero, sus programas son válidos para el estándar C90, pero no para los siguientes. int implícito (que permite declarar una función sin dar su tipo de retorno), y la declaración implícita de funciones (que permite usar una función sin declararla) ya no son válidas.

Segundo, eso no funciona como piensas.

  1. El tipo de resultado es opcional en C90, no dar uno significa un intresultado. Que también es cierto para la declaración de variables (pero debe dar una clase de almacenamiento, statico extern).

  2. Lo que hace el compilador cuando ve que Func_ise llama sin una declaración previa, es asumir que hay una declaración

    extern int Func_i();

    no busca más en el código para ver qué tan efectivamente Func_ise declara. Si Func_ino fue declarado o definido, el compilador no cambiaría su comportamiento al compilar main. La declaración implícita es solo para función, no hay ninguna para variable.

    Tenga en cuenta que la lista de parámetros vacía en la declaración no significa que la función no toma parámetros (debe especificar (void)para eso), significa que el compilador no tiene que verificar los tipos de parámetros y será el mismo conversiones implícitas que se aplican a argumentos pasados ​​a funciones variadas.

Un programador
fuente
Si el compilador puede encontrar el valor devuelto por Func_i () saliendo del main () y buscando el código fuente, entonces ¿por qué no puede encontrar el tipo de Func_i (), que se menciona explícitamente?
user106313
1
@ user31782 Si no hubo declaración previa de Func_i, al ver que Func_i se usa en una expresión de llamada, se comporta como si hubiera una extern int Func_i(). No se ve a ninguna parte.
Programador
1
@ user31782, el compilador no salta a ninguna parte. Emitirá código para llamar a esa función; El valor devuelto se determinará en tiempo de ejecución. Bueno, en el caso de una función tan simple que está presente en la misma unidad de compilación, la fase de optimización puede alinear la función, pero eso no es algo en lo que deba pensar al considerar las reglas del lenguaje, es una optimización.
Programador
10
@ user31782, tiene serios malentendidos sobre cómo funcionan los programas. Tan serio que no creo que p.se sea un buen lugar para corregirlos (tal vez el chat, pero no intentaré hacerlo).
Programador
1
@ user31782: Escribir un pequeño fragmento y compilarlo -S(si lo está usando gcc) le permitirá ver el código de ensamblaje generado por el compilador. Luego puede tener una idea de cómo se manejan los valores de retorno en tiempo de ejecución (normalmente usando un registro de procesador o algo de espacio en la pila de programas).
Giorgio
7

Escribiste en un comentario:

La ejecución se realiza línea por línea. La única forma de encontrar el valor devuelto por Func_i () es saltar fuera de la ventana principal

Esa es una idea falsa: la ejecución no es don línea por línea. La compilación se realiza línea por línea, y la resolución de nombres se realiza durante la compilación, y solo resuelve nombres, no devuelve valores.

Un modelo conceptual útil es este: cuando el compilador lee la línea:

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

emite un 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"

El compilador también hace una nota en alguna tabla interna que function #2es una función aún no declarada llamada Func_i, que toma un número no especificado de argumentos y devuelve un int (el valor predeterminado).

Más tarde, cuando analiza esto:

 int Func_i() { ...

el compilador busca Func_ien la tabla mencionada anteriormente y comprueba si los parámetros y el tipo de retorno coinciden. Si no lo hacen, se detiene con un mensaje de error. Si lo hacen, agrega la dirección actual a la tabla de funciones internas y pasa a la siguiente línea.

Entonces, el compilador no "buscó" Func_icuándo analizó la primera referencia. Simplemente hizo una nota en alguna tabla, y continuó analizando la siguiente línea. Y al final del archivo, tiene un archivo de objeto y una lista de direcciones de salto.

Más tarde, el enlazador toma todo esto y reemplaza todos los punteros a la "función # 2" con la dirección de salto real, por lo que emite 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

Mucho más tarde, cuando se ejecuta el archivo ejecutable, la dirección de salto ya está resuelta y la computadora puede saltar a la dirección 0x1215. No se requiere búsqueda de nombre.

Descargo de responsabilidad : como dije, ese es un modelo conceptual, y el mundo real es más complicado. Los compiladores y enlazadores hacen todo tipo de optimizaciones locas hoy. Incluso podrían "subir y bajar" para buscar Func_i, aunque lo dudo. Pero los lenguajes C se definen de una manera que podría escribir un compilador súper simple como ese. Entonces, la mayoría de las veces, es un modelo muy útil.

nikie
fuente
Gracias por su respuesta. No puede el compilador emita el código:1. call "function #2", put the return-type onto the stack and put the return value on the stack?
user106313
1
(Cont.) También: ¿Qué pasa si usted escribió printf(..., Func_i()+1);? El compilador tiene que saber el tipo de Func_i, por lo que puede decidir si debe emitir una add integero una add floatinstrucción. Puede encontrar algunos casos especiales en los que el compilador podría continuar sin la información de tipo, pero el compilador tiene que funcionar para todos los casos.
nikie
44
@ user31782: Las instrucciones de la máquina, como regla, son muy simples: agregue dos registros enteros de 32 bits. Cargue una dirección de memoria en un registro entero de 16 bits. Salta a una dirección. Además, no hay tipos : puede cargar felizmente una ubicación de memoria que representa un número flotante de 32 bits en un registro entero de 32 bits y hacer algo de aritmética con él. (Rara vez tiene sentido). Así que no, no puede emitir un código de máquina así directamente. Podría escribir un compilador que haga todas esas cosas con comprobaciones de tiempo de ejecución y datos de tipo adicionales en la pila. Pero no sería un compilador de C.
nikie
1
@ usuario31782: Depende, IIRC. floatlos valores pueden vivir en un registro FPU, entonces no habría ninguna instrucción en absoluto. El compilador solo realiza un seguimiento de qué valor se almacena en cada registro durante la compilación, y emite cosas como "agregar constante 1 al registro FP X". O podría vivir en la pila, si no hay registros libres. Luego habría una instrucción "aumentar el puntero de pila en 4", y el valor sería "referenciado" como algo así como "puntero de pila - 4". Pero todas estas cosas solo funcionan si los tamaños de todas las variables (antes y después) en la pila se conocen en tiempo de compilación.
nikie
1
De toda la discusión que he llegado a este entendimiento: para que el compilador haga un código de ensamblaje plausible para cualquier declaración que incluya Func_i()o / y Data_i, tiene que determinar sus tipos; no es posible en lenguaje ensamblador realizar una llamada al tipo de datos. Necesito estudiar las cosas en detalle para estar seguro.
user106313
5

C y varios otros lenguajes que requieren declaraciones fueron diseñados en una era en la que el tiempo de procesador y la memoria eran caros. El desarrollo de C y Unix fue de la mano durante bastante tiempo, y este último no tenía memoria virtual hasta que apareció 3BSD en 1979. Sin el espacio adicional para trabajar, los compiladores tendían a ser asuntos de un solo paso porque no requieren la capacidad de mantener alguna representación del archivo completo en la memoria de una vez.

Los compiladores de un solo paso están, como nosotros, cargados con la incapacidad de ver el futuro. Esto significa que lo único que pueden saber con certeza es lo que se les ha dicho explícitamente antes de que se compile la línea de código. Está claro para cualquiera de nosotros que Func_i()se declara más adelante en el archivo fuente, pero el compilador, que opera en una pequeña porción de código a la vez, no tiene ni idea de que se acerca.

A principios de C (AT&T, K&R, C89), el uso de una función foo()antes de la declaración resultó en una declaración de facto o implícita de int foo(). Su ejemplo funciona cuando Func_i()se declara intporque coincide con lo que el compilador declaró en su nombre. Cambiarlo a cualquier otro tipo dará lugar a un conflicto porque ya no coincide con lo que el compilador eligió en ausencia de una declaración explícita. Este comportamiento se eliminó en C99, donde el uso de una función no declarada se convirtió en un error.

Entonces, ¿qué pasa con los tipos de retorno?

La convención de llamada para el código objeto en la mayoría de los entornos requiere conocer solo la dirección de la función que se llama, lo cual es relativamente fácil de manejar para los compiladores y enlazadores. La ejecución salta al inicio de la función y regresa cuando regresa. Cualquier otra cosa, en particular los arreglos de pasar argumentos y un valor de retorno, está determinada enteramente por la persona que llama y quien llama en un acuerdo llamado convención de llamada . Siempre y cuando ambos compartan el mismo conjunto de convenciones, es posible que un programa invoque funciones en otros archivos de objetos si se compilaron en cualquier lenguaje que comparta esas convenciones. (En informática científica, te encuentras con un montón de C llamando a FORTRAN y viceversa, y la capacidad de hacerlo proviene de tener una convención de llamadas).

Otra característica de principios de C fue que los prototipos como los conocemos ahora no existían. Podría declarar el tipo de retorno de una función (por ejemplo, int foo()), pero no sus argumentos (es decir, int foo(int bar)no era una opción). Esto existió porque, como se describió anteriormente, el programa siempre se atuvo a una convención de llamada que podría ser determinada por los argumentos. Si llamó a una función con el tipo incorrecto de argumentos, era una situación de basura, basura.

Debido a que el código objeto tiene la noción de un retorno pero no un tipo de retorno, un compilador tiene que conocer el tipo de retorno para manejar el valor devuelto. Cuando ejecuta las instrucciones de la máquina, todo son solo bits y al procesador no le importa si la memoria en la que está tratando de comparar doublerealmente tiene una int. Simplemente hace lo que pides, y si lo rompes, eres el propietario de ambas piezas.

Considere estos bits de código:

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

El código de la izquierda se reduce a una llamada foo()seguida de copiando el resultado proporcionado a través de la convención de llamada / retorno en cualquier lugar donde xesté almacenado. Ese es el caso fácil.

El código de la derecha muestra una conversión de tipo y es por eso que los compiladores necesitan conocer el tipo de retorno de una función. Los números de punto flotante no se pueden volcar en la memoria donde otro código esperará ver un intporque no hay conversión mágica que tenga lugar. Si el resultado final tiene que ser un número entero, debe haber instrucciones que guíen al procesador para realizar la conversión antes del almacenamiento. Sin conocer el tipo de retorno foo()antes de tiempo, el compilador no tendría idea de que el código de conversión es necesario.

Los compiladores de pasadas múltiples permiten todo tipo de cosas, una de las cuales es la capacidad de declarar variables, funciones y métodos después de su primer uso. Esto significa que cuando el compilador llega a compilar el código, ya ha visto el futuro y sabe qué hacer. Java, por ejemplo, exige múltiples pasadas en virtud del hecho de que su sintaxis permite la declaración después del uso.

Blrfl
fuente
Gracias por tu respuesta (+1). No conozco los detalles del funcionamiento interno del compilador, ensamblador, procesador, etc. La idea básica de mi pregunta es que si por fin digo (escribo) el valor de retorno de la función en el código fuente, después del uso de esa función, entonces el idioma permite que la computadora encuentre ese valor sin dar ningún error. Ahora, ¿por qué la computadora no puede encontrar el tipo de manera similar? ¿Por qué no se puede encontrar el tipo de Data_i como se encontró Func_i()el valor de retorno?
user106313
Todavía no estoy satisfecho. double foo(); int x; x = foo();simplemente da el error. Sé que no podemos hacer esto. Mi pregunta es que en la llamada a la función el procesador solo encuentra el valor de retorno; ¿por qué no puede encontrar también el tipo de retorno también?
user106313
1
@ user31782: no debería. Hay un prototipo para foo(), por lo que el compilador sabe qué hacer con él.
Blrfl
2
@ user31782: Los procesadores no tienen ninguna noción de tipo de retorno.
Blrfl
1
@ user31782 Para la pregunta en tiempo de compilación: es posible escribir un lenguaje en el que todo este tipo de análisis se pueda hacer en tiempo de compilación. C no es tal lenguaje. El compilador de C no puede hacerlo porque no está diseñado para hacerlo. ¿Podría haber sido diseñado de manera diferente? Claro, pero tomaría mucho más poder de procesamiento y memoria para hacerlo. La conclusión es que no lo fue. Fue diseñado de una manera que las computadoras del día podían manejar mejor.
Mr.Mindor