¿Por qué algunos programas en C están escritos en un gran archivo fuente?

88

Por ejemplo, la herramienta SysInternals "FileMon" del pasado tiene un controlador en modo kernel cuyo código fuente está completamente en un archivo de 4,000 líneas. Lo mismo para el primer programa de ping que se haya escrito (~ 2,000 LOC).

Salvado
fuente

Respuestas:

143

El uso de varios archivos siempre requiere una sobrecarga administrativa adicional. Uno tiene que configurar un script de compilación y / o un archivo MAKE con etapas de compilación y vinculación separadas, asegurarse de que las dependencias entre los diferentes archivos se administren correctamente, escribir un script "zip" para facilitar la distribución del código fuente por correo electrónico o descargar, y así en. Los IDE modernos de hoy en día suelen llevar mucha carga, pero estoy bastante seguro de que en el momento en que se escribió el primer programa de ping, no había ningún IDE disponible. Y para archivos tan pequeños como ~ 4000 LOC, sin un IDE que administre múltiples archivos para usted, la compensación entre los gastos generales mencionados y los beneficios del uso de múltiples archivos podría permitir a las personas tomar una decisión sobre el enfoque de un solo archivo.

Doc Brown
fuente
99
"Y para archivos tan pequeños como ~ 4000 LOC ..." Estoy trabajando como desarrollador JS en este momento. Cuando tengo un archivo de solo 400 líneas de código, me pongo nervioso por lo grande que se ha vuelto. (Pero tenemos docenas y docenas de archivos en nuestro proyecto.)
Kevin
36
@Kevin: un cabello en mi cabeza es muy poco, un cabello en mi sopa es demasiado ;-) AFAIK en varios archivos JS no causa tanta sobrecarga administrativa como en "C sin un IDE moderno".
Doc Brown
44
Sin embargo, @ Kevin JS es una bestia bastante diferente. JS se transmite a un usuario final cada vez que un usuario carga un sitio web y su navegador ya no lo tiene en caché. C solo tiene que transmitir el código una vez, luego la persona en el otro extremo lo compila y permanece compilado (obviamente, hay excepciones, pero ese es el caso de uso general esperado). También las cosas de C tienden a ser código heredado, ya que gran parte de los proyectos de "4000 líneas es normal" que la gente describe en los comentarios.
Pharap
55
@Kevin Ahora ve y mira cómo se escribe underscore.js (1700 loc, un archivo) y una miríada de otras bibliotecas que se distribuyen. Javascript es en realidad casi tan malo como C con respecto a la modularización y la implementación.
Voo
2
@Pharap Creo que se refería a usar algo como Webpack antes de implementar el código. Con Webpack, puede trabajar en varios archivos y luego compilarlos en un solo paquete.
Brian McCutchon
81

Porque C no es bueno en la modularización. Se vuelve desordenado (archivos de encabezado e #incluye, funciones externas, errores de tiempo de enlace, etc.) y cuantos más módulos traigas, más complicado será.

Los lenguajes más modernos tienen mejores capacidades de modularización en parte porque aprendieron de los errores de C y hacen que sea más fácil dividir su base de código en unidades más pequeñas y simples. Pero con C, puede ser beneficioso evitar o minimizar todos esos problemas, incluso si eso significa agrupar lo que de otra manera se consideraría demasiado código en un solo archivo.

Mason Wheeler
fuente
38
Creo que es injusto describir el enfoque C como "errores"; eran decisiones perfectamente razonables y razonables en el momento en que se tomaron.
Jack Aidley
14
Ninguna de esas cosas de modularización es particularmente complicada. Se puede hacer complicado por el estilo de codificación mal, pero no es difícil de entender o implementar, y nada de esto podría ser clasificado como "errores". La verdadera razón, según la respuesta de Snowman, es que la optimización en múltiples archivos de origen no era tan buena en el pasado, y que el controlador FileMon requiere un alto rendimiento. Además, contrario a la opinión del OP, esos no son archivos particularmente grandes.
Graham
8
@Graham Cualquier archivo de más de 1000 líneas de código debe tratarse como un olor a código.
Mason Wheeler
11
@JackAidley no es injusto en absoluto , tener algo que ser un error no es mutuamente exclusivo al decir que fue una decisión razonable en ese momento. Los errores son inevitables dada la información imperfecta y el tiempo limitado y deben aprenderse de no vergonzosamente ocultos o reclasificados para salvar la cara.
Jared Smith
8
Cualquiera que afirme que el enfoque de C no es un error no comprende cómo un archivo C aparentemente de diez líneas puede ser realmente un archivo de diez mil líneas con todos los encabezados #include: d. Esto significa que cada archivo en su proyecto es efectivamente de al menos diez mil líneas, sin importar cuánto es el recuento de líneas dado por "wc -l". Un mejor soporte para la modularidad reduciría fácilmente los tiempos de análisis y compilación en una pequeña fracción.
juhist
37

Además de las razones históricas, hay una razón para usar esto en el software moderno sensible al rendimiento. Cuando todo el código está en una unidad de compilación, el compilador puede realizar optimizaciones de todo el programa. Con unidades de compilación separadas, el compilador no puede optimizar todo el programa de ciertas maneras (por ejemplo, incluir cierto código).

El enlazador ciertamente puede realizar algunas optimizaciones además de lo que puede hacer el compilador, pero no todas. Por ejemplo: los enlazadores modernos son realmente buenos para eludir funciones sin referencia, incluso a través de múltiples archivos de objetos. Es posible que puedan realizar otras optimizaciones, pero nada como lo que un compilador puede hacer dentro de una función.

Un ejemplo bien conocido de un módulo de código fuente único es SQLite. Puede leer más sobre esto en la página de Amalgamación SQLite .

1. Resumen Ejecutivo

Más de 100 archivos de origen separados se concatenan en un solo archivo grande de código C llamado "sqlite3.c" y llamado "la amalgamación". La amalgamación contiene todo lo que una aplicación necesita para incorporar SQLite. El archivo de amalgamación tiene más de 180,000 líneas de largo y más de 6 megabytes de tamaño.

La combinación de todo el código para SQLite en un archivo grande hace que SQLite sea más fácil de implementar: solo hay un archivo para realizar un seguimiento. Y debido a que todo el código está en una sola unidad de traducción, los compiladores pueden hacer una mejor optimización entre procedimientos, lo que resulta en un código de máquina que es entre 5% y 10% más rápido.


fuente
15
Pero tenga en cuenta que los compiladores de C modernos pueden optimizar todo el programa de múltiples archivos fuente (aunque no si los compila primero en archivos de objetos individuales).
Davislor
10
@Davislor Mira el script de compilación típico: los compiladores no van a hacer eso de manera realista.
44
Es significativamente más fácil cambiar un script de compilación $(CC) $(CFLAGS) $(LDFLAGS) -o $(TARGET) $(CFILES)que mover todo a un solo archivo soudce. Incluso puede hacer la compilación de todo el programa como un objetivo alternativo al script de compilación tradicional que omite la recompilación de archivos fuente que no han cambiado, de manera similar a cómo las personas pueden desactivar la creación de perfiles y la depuración para el objetivo de producción. No tiene esa opción si todo está en un gran montón de fuentes. No es a lo que la gente está acostumbrada, pero no tiene nada de engorroso.
Davislor
99
La optimización de todo el programa @Davislor / optimización del tiempo de enlace (LTO) también funciona cuando "compila" el código en archivos de objetos individuales (dependiendo de lo que "compile" signifique para usted). Por ejemplo, el LTO de GCC agregará su representación de código analizado a los archivos de objetos individuales en el momento de la compilación, y en el momento del enlace usará ese en lugar del código de objeto (también presente) para volver a compilar y construir todo el programa. Así que esto funciona con configuraciones de compilación que primero se compilan a archivos de objetos individuales, aunque se ignora el código de máquina generado por la compilación inicial.
Dreamer
8
JsonCpp también hace esto hoy en día. La clave es que los archivos no son así durante el desarrollo.
Carreras de ligereza en órbita
15

Además del factor de simplicidad que mencionó el otro encuestado, muchos programas en C están escritos por un individuo.

Cuando tiene un equipo de personas, es deseable dividir la aplicación en varios archivos de origen para evitar conflictos gratuitos en los cambios de código. Especialmente cuando hay programadores avanzados y muy jóvenes trabajando en el proyecto.

Cuando una persona trabaja sola, eso no es un problema.

Personalmente, uso varios archivos basados ​​en la función como algo habitual. Pero solo soy yo.

Rublo de Ron
fuente
44
@OskarSkog Pero nunca modificará un archivo al mismo tiempo que su futuro yo.
Loren Pechtel
2

Porque C89 no tenía inlinefunciones. Lo que significaba que dividir su archivo en funciones causó la sobrecarga de empujar valores en la pila y saltar. Esto agregó un poco de sobrecarga sobre la implementación del código en 1 declaración de interruptor grande (bucle de eventos). Pero un bucle de eventos siempre es mucho más difícil de implementar de manera eficiente (o incluso correcta) que una solución más modularizada. Entonces, para proyectos de gran tamaño, las personas aún optarían por modularizar. Pero cuando tenían el diseño pensado de antemano y podían controlar el estado en una declaración de cambio, optaron por eso.

Hoy en día, incluso en C, no es necesario sacrificar el rendimiento para modularizar porque incluso en C las funciones se pueden incorporar.

Dmitry Rubanovich
fuente
2
Las funciones C podrían estar tanto en línea en 89 como en estos días, en línea es algo que debería usarse casi nunca: el compilador sabe mejor que usted en casi todas las situaciones. Y la mayoría de esos archivos 4k LOC no son una función gigantesca, es un estilo de codificación horrible que tampoco tendrá ningún beneficio de rendimiento notable.
Voo
@Voo, no sé por qué mencionas el estilo de codificación. No lo estaba defendiendo. De hecho, mencioné que en la mayoría de los casos garantiza una solución menos eficiente debido a una implementación fallida. También mencioné que es una mala idea porque no escala (para proyectos más grandes). Dicho esto, en bucles muy ajustados (que es lo que sucede en el código de red cercano al hardware), empujar y abrir valores innecesariamente en la pila de encendido / apagado (cuando se llaman funciones) aumentará el costo del programa en ejecución. Esta no fue una gran solución. Pero fue el mejor disponible en ese momento.
Dmitry Rubanovich
2
Nota obligatoria: la palabra clave en línea tiene poco que ver con la optimización de la línea. No es una pista especial para que el compilador haga esa optimización, sino que tiene que ver con la vinculación con símbolos duplicados.
hyde
@Dmitry El punto es que afirmar que debido a que no había una inlinepalabra clave en los compiladores de C89 no podía estar en línea, es por eso que tenía que escribir todo en una función gigante. Casi nunca debería usarlo inlinecomo una optimización del rendimiento: el compilador generalmente sabrá mejor que usted de todos modos (y puede ignorar la palabra clave).
Voo
@Voo: Un programador y un compilador generalmente sabrán algunas cosas que el otro no. La inlinepalabra clave tiene una semántica relacionada con el enlazador que es más importante que la cuestión de si realizar o no las optimizaciones en línea, pero algunas implementaciones tienen otras directivas para controlar la alineación y tales cosas a veces pueden ser muy importantes. En algunos casos, una función puede parecer demasiado grande para que valga la pena incluirla, pero el plegado constante puede reducir el tamaño y el tiempo de ejecución a casi nada. Un compilador al que no se le da un empujoncito para alentar la alineación podría no ...
supercat
1

Esto cuenta como un ejemplo de evolución, que me sorprende que aún no se haya mencionado.

En los días oscuros de programación, la compilación de un solo ARCHIVO podría llevar minutos. Si un programa se modularizó, la inclusión de los archivos de encabezado necesarios (sin opciones de encabezado precompiladas) sería una causa adicional significativa de desaceleración. Además, el compilador podría elegir / necesitar mantener cierta información en el disco, probablemente sin el beneficio de un archivo de intercambio automático.

Los hábitos que estos factores ambientales llevaron a las prácticas de desarrollo en curso y se han ido adaptando lentamente con el tiempo.

En ese momento, la ganancia de usar un solo archivo sería similar a la que obtenemos mediante el uso de SSD en lugar de HDD.

itj
fuente