2010-04-13 30 views
15

Duplicar posible:
Should C++ eliminate header files?¿Por qué son necesarias las declaraciones anticipadas?

En lenguajes como C# y Java no hay necesidad de declarar (por ejemplo) una clase antes de usarlo. Si lo entiendo correctamente, esto se debe a que el compilador realiza dos pasadas en el código. En el primero, simplemente "recopila la información disponible" y en el segundo, verifica que el código sea correcto.

En C y C++ el compilador solo hace una pasada, por lo que todo debe estar disponible en ese momento.

Así que mi pregunta es básicamente por qué no se hace de esta manera en C y C++. ¿No eliminaría las necesidades de archivos de encabezado?

+0

El compilador de C++ leerá secuencialmente [es decir, que se lee de arriba a abajo. ...] Así es como funciona el lenguaje. Su sugerencia de "pasar" dos veces y observar los prototipos de la función funcionaría, pero desafortunadamente no es así como funciona el lenguaje. – Warty

+2

Duplicado: http://stackoverflow.com/questions/752793 –

+4

C como un lenguaje estandarizado hace más de 30 años, cuando la tecnología subyacente era mucho menos capaz y mucho más cara que las cosas de hoy. Una sugerencia suave: trate de aprender un poco sobre la historia de las cosas, y entienda que el mundo ha cambiado tremendamente desde aquellos Las decisiones se tomaron y continuarán cambiando después de tomar decisiones en sus esfuerzos de desarrollo. Alguien, algún día, * se preguntará "¡¿WTF ?!" acerca de sus decisiones ... ;-) – DaveE

Respuesta

39

La respuesta breve es que la potencia de cálculo y los recursos avanzaron exponencialmente entre el momento en que se definió C y el momento en que apareció Java 25 años después.

La respuesta más larga ...

El tamaño máximo de una unidad de compilación - el bloque de código que procesa el compilador en un solo trozo - va a estar limitado por la cantidad de memoria que la computadora que compila tiene. Para procesar los símbolos que escribe en código máquina, el compilador necesita mantener todos los símbolos en una tabla de búsqueda y hacer referencia a ellos cuando los encuentre en su código.

Cuando C se creó en 1972, los recursos informáticos eran mucho más escasos y de mayor valor: la memoria necesaria para almacenar toda la tabla simbólica de un programa complejo simplemente no estaba disponible en la mayoría de los sistemas. El almacenamiento fijo también era costoso y extremadamente lento, por lo que ideas como la memoria virtual o el almacenamiento de partes de la tabla simbólica en el disco simplemente no habrían permitido la compilación en un plazo razonable.

La mejor solución al problema consistía en dividir el código en partes más pequeñas ordenando a los seres humanos qué partes de la tabla de símbolos se necesitarían con qué unidades de compilación por anticipado.La imposición de una tarea bastante pequeña en el programador de declarar lo que usaría ahorró el tremendo esfuerzo de hacer que la computadora buscara todo el programa para cualquier cosa que el programador pudiera usar.

También guardó el compilador de tener que hacer dos pasadas en cada archivo fuente: el primero para indexar todos los símbolos dentro, y el segundo para analizar las referencias y buscarlas. Cuando se trata de cinta magnética donde los tiempos de búsqueda se midieron en segundos y el rendimiento de lectura se midió en bytes por segundo (no kilobytes o megabytes), eso fue bastante significativo.

C++, aunque se creó casi 17 años más tarde, se definió como un superconjunto de C, y por lo tanto tuvo que usar el mismo mecanismo.

En el momento en que Java rodó en 1995, las computadoras promedio tenían suficiente memoria que mantener una tabla simbólica, incluso para un proyecto complejo, ya no era una carga sustancial. Y Java no fue diseñado para ser compatible con versiones anteriores con C, por lo que no tuvo necesidad de adoptar un mecanismo heredado. C# estaba igualmente libre de cargas.

Como resultado, sus diseñadores optaron por cambiar la carga de la declaración simbólica de compartimentación del programador y volver a ponerla en la computadora, ya que su costo en proporción al esfuerzo total de compilación fue mínimo.

+7

Excelente resumen. Me recordó los "buenos viejos tiempos" compilando un programa C en una PC con 2 disquetes 640K - Tardé aproximadamente 10 minutos con una media docena o más de cambios de disquete. ¡Todo eso para un programa que contiene no más de un par de cientos de declaraciones! Y pensé que estaba en el cielo con todo ese poder. – NealB

+4

¡Gran respuesta, siempre es bueno tener una perspectiva histórica para nosotros los jóvenes! –

5

Conclusión: ha habido avances en la tecnología del compilador que hacen que las declaraciones no sean necesarias. Además, las computadoras son miles de veces más rápidas, por lo que pueden hacer los cálculos adicionales necesarios para manejar la falta de declaraciones directas.

C y C++ son más antiguos y se estandarizaron en un momento en que era necesario guardar cada ciclo de CPU.

+2

:-) En otras palabras, C# es mejor que C++. –

+8

Aquí faltan las palabras clave: compatibilidad con versiones anteriores. Su última línea lo hace sonar como C y C++ tienen solo una versión del estándar de las edades de piedra. Debería leer "y fueron * primero * estandarizados ... y para mantener la compatibilidad con versiones anteriores, el método sigue siendo el mismo". @Franci: Cuando termines de escribir un sistema operativo en C#, ven a buscarme. – GManNickG

+3

@Franci: No ... en otras palabras, los compiladores de idiomas modernos han dejado obsoletas las declaraciones avanzadas porque no tienen que preocuparse por la compatibilidad con versiones anteriores. * Se puede * hacer * en C++. Diviértete escribiendo drivers de hardware en C# bud. –

1

Esto se debe a los módulos de compilación más pequeños en C/C++. En C/C++, cada archivo .c/.cpp se compila por separado, creando un módulo .obj. Por lo tanto, el compilador necesita la información sobre tipos y variables, declarada en otros módulos de compilación. Esta información se proporciona en forma de declaraciones directas, generalmente en archivos de encabezado.

C#, en el otro lado, compila varios archivos .cs en un gran módulo de compilación a la vez.

De hecho, al hacer referencia a diferentes módulos compilados de un programa C#, el compilador necesita conocer las declaraciones (nombres de tipos, etc.) de la misma manera que lo hace el compilador C++. Esta información se obtiene directamente del módulo compilado. En C++, la misma información está explícitamente separada (es por eso que no puede encontrar los nombres de las variables de la DLL compilada en C++, pero puede determinarlo desde el ensamblado de .NET).

+0

Desafortunadamente, eso no explica cómo C# administra nuestra solución de 100 ensamblajes que contiene miles de archivos fuente y millones de líneas de código mejor que C++ administra una sola.h archivo (que aún puede necesitar una declaración directa aunque toda la información que necesita esté en el archivo). –

+0

@Jason: los beneficios de la compilación separada son diferentes: cuando se cambia solo la implementación, la recompilación será casi instantánea. (Por supuesto, esto hace que la primera compilación sea más lenta.) No sé por qué su C++ no puede gestionar un solo archivo de encabezado, nunca tuve ningún problema con el mío. – Vlad

+0

@Vlad: Usted dijo "... el compilador necesita información sobre los tipos ... declarados en otros módulos de compilación", pero de hecho, incluso una sola clase C++ en un solo encabezado * puede * requerir predeclaración, es decir, incluso dentro de un módulo de compilación . Es decir, C++ analiza de forma lineal el código (por lo que requiere una predeclaración de tipos futuros cuando se hace referencia a ellos), mientras que C# construye de manera efectiva una base de datos para la base de código que le permite tener acceso aleatorio a todos los tipos. –

0

Las declaraciones de reenvío en C++ son una forma de proporcionar metadatos sobre los otros fragmentos de código que el origen actualmente compilado podría usar para el compilador, por lo que puede generar el código correcto.

Los metadatos pueden provenir del autor de la biblioteca/componente vinculado. Sin embargo, también se puede generar automáticamente (por ejemplo, hay herramientas que generan archivos de cabecera C++ para objetos COM). En cualquier caso, la forma C++ de expresar esos metadatos es a través de los archivos de encabezado que debe incluir en su código fuente.

La C# /. Net también consume metadatos similares en tiempo de compilación. Sin embargo, esos metadatos se generan automáticamente cuando el ensamblaje al que se aplica se genera y generalmente está incorporado en él. Por lo tanto, cuando hace referencia en su proyecto de C# a un ensamblaje, básicamente le está diciendo al compilador "busque también los metadatos que necesita en este ensamblaje, por favor".

En otras palabras, la generación y el consumo de metadatos en C# es más transparente para los desarrolladores, lo que les permite concentrarse en lo que realmente importa: escribir su propio código.

También hay otros beneficios que tienen los metadatos sobre el código incluido con el ensamblaje. Serialización de reflexión, emisión de código, sobre la marcha: todos ellos dependen de los metadatos para poder generar el código adecuado en tiempo de ejecución.

El análogo de C++ a esto sería RTTI, aunque no es ampliamente adoptado debido a implementaciones incompatibles.

0

Eric Lippert, blogger de todas las cosas internas de C#: http://blogs.msdn.com/ericlippert/archive/2010/02/04/how-many-passes.aspx:

El lenguaje C# no requiere que declaraciones se producen antes de los usos, que tiene dos impactos, de nuevo, en el usuario y en el escritor del compilador [...]

El impacto en el escritor del compilador es que tenemos que tener un compilador de "dos pasos" . En la primera pasada, buscamos para declaraciones e ignoramos cuerpos. Una vez que hemos recogido toda la información de las declaraciones que nos darían de las cabeceras en C++, tomamos una segunda pasada sobre el código y generar el IL de los cuerpos .

En resumen, usar algo no requiere declararlo en C#, mientras que en C++. Eso significa que en C++, necesita declarar explícitamente las cosas, y es más conveniente y seguro hacerlo con los archivos de encabezado para no violar el One Definition Rule.

3

No, no obviaría los archivos de encabezado. Eliminaría el requisito de usar un encabezado para declarar clases/funciones en el mismo archivo. La principal razón para los encabezados es no para declarar cosas en el mismo archivo. La razón principal para los encabezados es declarar cosas que están definidas en otros archivos.

Para bien o para mal, las reglas para la semántica de C (y C++) imponen el comportamiento de estilo de "paso único". Sólo por ejemplo, considere un código como éste:

int i; 

int f() { 
    i = 1; 
    int i = 2; 
} 

Los i=1 asigna al mundial, no la definida en el interior de f(). Esto se debe a que en el momento de la asignación, la definición local de i aún no se ha visto, por lo que no se tiene en cuenta. Todavía podría seguir estas reglas con un compilador de dos pasos, pero hacerlo podría no ser trivial. No he comprobado sus especificaciones para saber con certeza, pero mi conjetura inmediata sería que Java y C# difieren de C y C++ a este respecto.

Editar: Desde que un comentario dijo que mi conjetura era incorrecta, hice un poco de comprobación. De acuerdo con la Referencia del lenguaje Java, §14.4.2, Java parece seguir bastante cercano a las mismas reglas que C++ (un poco diferente, pero no mucho.

Al menos mientras leía el C# language specification, (advertencia: archivo de Word) sin embargo, es diferente. (§3.7.1) dice: "El alcance de una variable local declarada en una declaración de variable local (§8.5.1) es el bloque en el que la declaración ocurre. "

Esto parece decir que en C#, la variable local debe ser visible en todo el entero bloque en el que se declara, por lo que con un código similar al ejemplo que di, el nombre nt sería a la variable local, no a la global.

lo tanto, mi suposición era mitad derecha: Java sigue (bastante much0 la misma regla como C++, en este sentido, pero C# no lo hace

+0

Supongo que sería incorrecto. –

Cuestiones relacionadas