2010-09-21 5 views
24

estaba leyendo Wikipedia sobre las declaraciones de C/C++ Prototype y estoy confundido:Propósito de C/C Prototipos ++

Wikipedia dice: "Al incluir el prototipo de función, se informa al compilador que la función 'fac' toma un argumento entero y habilita al compilador para detectar este tipo de errores ".

y utiliza el siguiente ejemplo:

#include <stdio.h> 

/* 
    * If this prototype is provided, the compiler will catch the error 
    * in main(). If it is omitted, then the error will go unnoticed. 
    */ 
int fac(int n);    /* Prototype */ 

int main(void) {    /* Calling function */ 
    printf("%d\n", fac()); /* ERROR: fac is missing an argument! */ 
    return 0; 
} 

int fac(int n) {    /* Called function */ 
    if (n == 0) 
     return 1; 
    else 
     return n * fac(n - 1); 
} 

embargo, la definición de la función de la función de llamada ya incluye todos los la información que el prototipo le dice al compilador, así que por qué no se puede deducir que el compilador esta información de la definición de la función llamada ya que contienen idénticas declaraciones/carta de información para la carta?

¿Qué me estoy perdiendo? Parece un trabajo extra sin ganancia obvia.

Edit: Gracias chicos. Supuse que los compiladores eran multi-pass, supongo. Estoy mimado con los idiomas actuales, como Python. Tiene sentido ya que es tan viejo que necesita algunos kludges para hacer las cosas con precisión en una sola pasada. Me parece más obvio ahora. Aparentemente, requiere un conocimiento bastante íntimo de cómo el compilador vincula y compila.

+7

Tenga en cuenta que este artículo de wikipedia contiene cosas incorrectas. Puedes ver en la página de discusión cómo algunos chicos simplemente no permiten que se corrija. Me di por vencido en eso. –

+0

Es por eso que defino funciones antes de que se llamen (en el mismo archivo fuente, de todos modos). Elimina la necesidad de una declaración por separado, aunque significa que mi código dice "al revés". –

Respuesta

16

Los prototipos le permiten separar la interfaz de la implementación.

En su ejemplo, todo el código reside en un archivo, y podría haber movido la definición de fac() hasta donde el prototipo está actualmente y eliminar el prototipo.

Los programas del mundo real se componen de múltiples archivos .cpp (también conocidos como unidades de compilación), compilados con frecuencia y vinculados en bibliotecas antes de vincularlos al formato ejecutable final. Para proyectos a gran escala de esa naturaleza, los prototipos se recopilan en archivos .h (también conocidos como archivos de encabezado), donde el encabezado se incluye en otras unidades de compilación en tiempo de compilación para alertar al compilador sobre las convenciones de existencia y llamada de la funcionalidad en la biblioteca. En estos casos, la definición de función no está disponible para el compilador, por lo que los prototipos (declaraciones aka) sirven como una especie de contrato que define las capacidades y los requisitos de la biblioteca.

3

El compilador de C procesa archivos de origen de arriba a abajo. Las funciones que aparecen después de su uso no se tienen en cuenta al resolver tipos de argumentos. Así, en su ejemplo, si main() estaba en el fondo del archivo, entonces sería no necesita un prototipo para fac() (ya que la definición de fac() ya habría sido visto por el compilador al compilar main()).

26

dos razones:

  1. El compilador lee el archivo de arriba a abajo. Si fac se utiliza en main que está por encima fac, y no existe un prototipo, el compilador no sabe cómo comprobar que esa llamada se realiza correctamente, ya que aún no ha llegado a la definición de fac.

  2. Es posible dividir un programa C o C++ en varios archivos. fac se puede definir en un archivo completamente diferente del archivo que el compilador está procesando actualmente, por lo que necesita saber que esa función existe en alguna parte, y cómo se supone que debe invocarse.

Tenga en cuenta que los comentarios en el ejemplo solamente se anotaron aplican a C. En C++, ese ejemplo se siempre producir un error, incluso si se omite el prototipo (aunque se producirá un error diferente dependiendo si el prototipo existe o no). En C++, se requiere que todas las funciones sean definidas o prototipadas antes de ser utilizadas.

En C, puede omitir el prototipo y el compilador le permitirá llamar a la función con cualquier cantidad de argumentos (incluido el cero), y asumirá un tipo de devolución de int. Pero el hecho de que no le grite durante la compilación no significa que el programa funcionará correctamente si no llama a la función de la manera correcta. Es por eso que es útil hacer un prototipo en C: para que el compilador pueda verificarlo en su nombre.

La filosofía detrás de C y C++ que motiva este tipo de características es que estos son lenguajes de bajo nivel. No hacen mucho de la mano y no hacen mucho si se realiza una comprobación en tiempo de ejecución. Si su programa hace algo incorrecto, se bloqueará o se comportará de manera extraña. Por lo tanto, los lenguajes incorporan características como esta que permiten al compilador identificar ciertos tipos de errores en tiempo de compilación, para que pueda encontrarlos y solucionarlos más fácilmente.

+0

En caso de que el # 2 no incluya o algo de ese tipo, dígale en qué archivo encontrar la función real. En Python solo importarías el módulo y luego harías referencia a la función como función de módulo. ¿C no tiene espacios de nombres a los que se pueda hacer referencia de esta manera? – pythonnewbie

+0

¿Cómo resuelve # 1? ¿Nada en el prototipo le dice al compilador que las llamadas fac principales y/o las llamadas principales fac? – pythonnewbie

+0

En el caso n. ° 2, ¿qué crees que incluye? ¡Ellos contienen prototipos! Pero si está escribiendo su propia función 'fac', * usted * necesita proporcionar el prototipo, ya sea en un archivo separado incluido o en el mismo archivo fuente. En el caso n. ° 1 (supongo que se refiere a la respuesta de Mystagogue aquí), el problema no es que haya algo incorrecto en una dependencia recursiva, sino que si tiene una dependencia recursiva, no hay forma de ordenar las dos funciones. tal que el compilador lea la definición de ambos antes de procesar una llamada a cualquiera de ellos. –

4

La razón más importante para el prototipo es resolver dependencias circulares. Si "main" puede llamar a "fac" y "fac" a "main", entonces necesitará un prototipo para resolverlo.

+0

Esto, junto con el enlace a una biblioteca ya compilada, son realmente las dos razones por las que la declaración es necesaria.Todos los demás son cosméticos/preferencias. – codechimp

1

Además de todas las buenas respuestas ya dadas, piensa en eso: si fueras un compilador, y tu trabajo fuera traducir el código fuente en lenguaje de máquina, y tú (siendo el compilador obediente que eres) solo podrías leer una fuente código línea por línea: ¿cómo leería el código que pegó si no hubiera un prototipo? ¿Cómo sabría usted que la llamada a función es válida y no un error de sintaxis? (Sí, puedes hacer una nota y verificar al final si todo coincide, pero esa es otra historia).

Otra forma de verlo (esta vez como un ser humano): que no tiene la función definida como un prototipo suponer, ni es su código fuente disponible. Sabes, sin embargo, que en la biblioteca que tu compañero te entregó está el código máquina que, cuando se ejecuta, devuelve un determinado comportamiento esperado. Que agradable. Ahora, ¿cómo sabría el compilador que esa llamada a funciones es válida si no hay un prototipo que diga "oye amigo, créeme, hay una función llamada tal y tal que toma parámetros y devuelve algo"?

Sé que es una manera muy, muy, muy simple de pensar al respecto. Agregar intencionalidad a piezas de software es probablemente una mala señal, ¿no?

4

C y C++ son dos idiomas diferentes, y en este caso particular hay una gran diferencia entre los dos. Del contenido de la pregunta Asumo que usted está hablando de C.

#include <stdio.h> 
int main() { 
    print(5, "hi"); // [1] 
} 
int print(int count, const char* txt) { 
    int i; 
    for (i = 0; i < count; ++i) 
     printf("%s\n", txt); 
} 

Eso es un programa C adecuado que hace lo que puede esperar: grabados 5 líneas diciendo "hola" en cada uno de ellos. El lenguaje C encuentra la llamada en [1] asume que print es una función que devuelve int y toma un número desconocido de argumentos (desconocido para el compilador, conocido por el programador), el compilador asume que la llamada es correcta y continúa compilando Desde la definición de la función y la coincidencia de llamada, el programa está bien formado.

El problema es que cuando el compilador analiza la línea en [1] no puede realizar ningún tipo de comprobación de tipo, ya que no sabe cuál es la función. Si cuando escribimos esta línea confundimos el orden de los argumentos y escribimos print("hi", 5);, el compilador seguirá aceptando la línea, ya que no tiene conocimiento previo de print. Como la llamada es incorrecta, incluso si el código se compila, fallará más adelante.

Al declarar la función de antemano, proporciona al compilador la información necesaria para verificar en el lugar de la llamada. Si la declaración está presente, y se comete el mismo error, el compilador detectará el error y le informará de su error.

En C++, por otro lado, el compilador no asumirá que la llamada es correcta y realmente requerirá para proporcionar una declaración de la función antes de la llamada.