c++ - librerias - ¿Por qué es necesario declarar las funciones antes de utilizarlas?
tipos de datos en c++ (11)
Cuando leí algunas respuestas a esta pregunta , comencé a preguntarme por qué el compilador realmente necesita saber acerca de una función cuando la encuentra por primera vez. ¿No sería sencillo simplemente agregar un pase adicional al analizar una unidad de compilación que recopila todos los símbolos declarados en el interior, de modo que el orden en el que se declaran y se usan ya no importa?
Se podría argumentar que declarar funciones antes de que se usen es ciertamente un buen estilo, pero me pregunto si hay alguna otra razón por la que esto sea obligatorio en C ++.
Editar: un ejemplo para ilustrar: suponga que tiene funciones definidas en línea en un archivo de encabezado. Estas dos funciones se llaman una a la otra (tal vez un recorrido recursivo del árbol, donde las capas pares e impares se manejan de manera diferente). La única forma de resolver esto sería hacer una declaración hacia adelante de una de las funciones antes de la otra.
Un ejemplo más común (aunque con clases, no funciones) es el caso de clases con constructores y fábricas private
. La fábrica necesita conocer la clase para crear instancias de la misma, y la clase necesita conocer la fábrica para la declaración del friend
.
Si este es un requisito de los días anteriores, ¿por qué no se eliminó en algún momento? No rompería el código existente, ¿verdad?
¿Cómo propone resolver los identificadores no declarados que se definen en una unidad de traducción diferente ?
C ++ no tiene un concepto de módulo, pero tiene una traducción separada como herencia de C. Un compilador de C ++ compilará cada unidad de traducción por sí mismo, sin saber nada acerca de otras unidades de traducción. (Excepto que la export
rompió esto, que es probablemente la razón por la que, por desgracia, nunca despegó).
Los archivos de encabezado , que suelen incluir declaraciones de identificadores que se definen en otras unidades de traducción, en realidad son una forma muy torpe de deslizar las mismas declaraciones en diferentes unidades de traducción. No informarán al compilador de que hay otras unidades de traducción con identificadores definidos en ellas.
Edite sus ejemplos adicionales:
Con toda la inclusión textual en lugar de un concepto de módulo adecuado, la compilación ya lleva un tiempo agonizante para C ++, por lo que requerir otro pase de compilación (donde la compilación ya está dividida en varios pases, no todos ellos pueden optimizarse y fusionarse, IIRC) empeoraría ya mal problema Y cambiar esto probablemente alteraría la resolución de sobrecarga en algunos escenarios y, por lo tanto, rompería el código existente.
Tenga en cuenta que C ++ requiere un pase adicional para analizar las definiciones de clase, ya que las funciones miembro definidas en línea en la definición de clase se analizan como si estuvieran definidas justo detrás de la definición de clase. Sin embargo, esto se decidió cuando se inventó C con Clases, por lo que no había una base de código existente para romper.
Aún así, puede utilizar una función antes de que se declare algunas veces (para ser estricto en la redacción: "antes" es sobre el orden en que se lee la fuente del programa), ¡dentro de una clase !:
class A {
public:
static void foo(void) {
bar();
}
private:
static void bar(void) {
return;
}
};
int main() {
A::foo();
return 0;
}
(Cambiar la clase a un espacio de nombres no funciona, por mis pruebas).
Probablemente sea porque el compilador en realidad coloca las definiciones de la función miembro desde dentro de la clase justo después de la declaración de la clase, como alguien lo ha señalado aquí en las respuestas.
El mismo enfoque podría aplicarse a todo el archivo fuente: primero, elimine todo menos la declaración, luego maneje todo lo pospuesto. (Ya sea un compilador de dos pasadas o una memoria lo suficientemente grande como para contener el código fuente pospuesto).
¡Jaja! Entonces, pensaron que un archivo fuente completo sería demasiado grande para guardar en la memoria, pero una sola clase con definiciones de funciones no lo haría : pueden permitir que una clase entera se quede en la memoria y esperar hasta que la declaración se filtre ( o hacer una segunda pasada para el código fuente de las clases)!
Como C ++ es un lenguaje estático, el compilador necesita verificar si el tipo de valores es compatible con el tipo esperado en los parámetros de la función. Por supuesto, si no conoce la firma de la función, no puede hacer este tipo de controles, desafiando así el propósito de un compilador estático. Pero, como tienes una insignia de plata en C ++, creo que ya lo sabes.
Las especificaciones del lenguaje C ++ se hicieron correctamente porque el diseñador no quería forzar un compilador de múltiples pases, cuando el hardware no era tan rápido como el que estaba disponible en la actualidad. Al final, creo que, si C ++ se diseñara hoy, esta imposición desaparecería, pero entonces tendríamos otro lenguaje :-).
El lenguaje de programación C se diseñó para que el compilador pudiera implementarse como un compilador de una pasada . En tal compilador, cada fase de compilación solo se ejecuta una vez. En un compilador de este tipo, no puede hacer referencia a una entidad que se define más adelante en el archivo fuente.
Además, en C, el compilador solo interpreta una sola unidad de compilación (generalmente un archivo .c y todos los archivos .h incluidos) a la vez. Así que necesitabas un mecanismo para referirte a una función definida en otra unidad de compilación.
La decisión de permitir un compilador de una sola pasada y poder dividir un proyecto en una pequeña unidad de compilación se tomó porque en ese momento la memoria y la capacidad de procesamiento disponible eran realmente limitadas. Y permitir una declaración avanzada podría resolver fácilmente el problema con una sola función.
El lenguaje C ++ se derivó de C y heredó la característica de él (ya que quería ser lo más compatible posible con C para facilitar la transición).
Históricamente el C89 te permite hacer esto. La primera vez que el compilador vio el uso de una función y no tenía un prototipo predefinido, "creó" un prototipo que coincidía con el uso de la función.
Cuando C ++ decidió agregar una comprobación de tipos estricta al compilador, se decidió que ahora se necesitaban prototipos. Además, C ++ heredó la compilación de una sola pasada de C, por lo que no pudo agregar una segunda pasada para resolver todos los símbolos.
La razón principal será hacer que el proceso de compilación sea lo más eficiente posible. Si agrega un pase adicional, agrega tiempo y almacenamiento. Recuerde que C ++ se desarrolló antes del tiempo de los procesadores Quad Core :)
Pienso en dos razones:
- Facilita el análisis. No se necesita pase extra.
- También define el alcance ; Los símbolos / nombres están disponibles solo después de su declaración. Significa, si declaro una variable global
int g_count;
, el código posterior después de esta línea puede usarlo, ¡pero no el código anterior a la línea! Mismo argumento para las funciones globales.
Como ejemplo, 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!
}
Salida:
vacío g (doble)
vacío g (int)
Vea la salida en ideone: http://www.ideone.com/EsK4A
Porque C y C ++ son lenguajes antiguos . Los primeros compiladores no tenían mucha memoria, por lo que estos idiomas se diseñaron para que un compilador pueda leer el archivo de arriba a abajo, sin tener que considerar el archivo como un todo .
Recuerdo que con Unix y Linux, tienes Global
y Local
. Dentro de su propio entorno, local trabaja para funciones, pero no funciona para Global(system)
. Debes declarar la función Global
.
Supongo que porque C es bastante antiguo y en el momento en que C se diseñó, la compilación eficiente fue un problema porque las CPU eran mucho más lentas.
Una de las razones más importantes por las que esto se hizo obligatorio incluso en C99 (en comparación con C89, donde podría haber declarado funciones implícitamente) es que las declaraciones implícitas son muy propensas a errores. Considere el siguiente código:
Primer archivo:
#include <stdio.h>
void doSomething(double x, double y)
{
printf("%g %g/n",x,y);
}
Segundo archivo:
int main()
{
doSomething(12345,67890);
return 0;
}
Este programa es un programa sintácticamente válido * C89. Puede compilarlo con GCC usando este comando (asumiendo que los archivos de origen se llaman test.c
y test0.c
):
gcc -std=c89 -pedantic-errors test.c test0.c -o test
¿Por qué imprime algo extraño (al menos en linux-x86 y linux-amd64)? ¿Puedes detectar el problema en el código de un vistazo? Ahora intente reemplazar c89
con c99
en la línea de comandos, y el compilador le notificará de inmediato su error.
Lo mismo con C ++. Pero en C ++ hay otras razones importantes por las que se necesitan declaraciones de funciones, se analizan en otras respuestas.
* Pero tiene un comportamiento indefinido.