¿Por qué la programación en C necesita un compilador y los scripts de shell no?

8

Escribí un script bash y lo ejecuté sin compilarlo primero. Funcionó perfectamente. Puede funcionar con o sin permisos, pero cuando se trata de programas en C, necesitamos compilar el código fuente. ¿Por qué?

Mestizo
fuente
99
Significa que C es un lenguaje compilado y Bash es un lenguaje interpretado. Nada más y nada menos.
Radon Rosborough
10
Estoy votando para cerrar esta pregunta como fuera de tema porque no está relacionada con U&L, e incluso si lo fuera, es demasiado amplia ya que uno puede discutirla interminablemente sin proporcionar una respuesta satisfactoria, sin hablar de una respuesta que se ajuste al idea general de U&L como base de conocimiento para resolver problemas.
contramode
44
@countermode Usted apila (intercambia | desborda) personas y sus votos cerrados de activación feliz. La pregunta no es amplia: revela una comprensión faltante muy específica: la diferencia entre los lenguajes compilados e interpretados. Esto requiere un par de párrafos para explicar, y otro par de párrafos para señalar las (des) ventajas de cada uno, más una nota al pie para resumir que C y bash tenían diferentes objetivos, por lo que eligieron los diferentes enfoques.
mtraceur
2
@mtraceur Lo siento, no quiero ofender a nadie. A partir de los votos de cierre feliz, esto es un poco injusto. Se requieren cinco votos para cerrar una pregunta, y si alguien más vota para no cerrarla, estaré de acuerdo. Tiene toda la razón sobre la pregunta, sin embargo, no estoy seguro de si esas preguntas pertenecen a U&L de acuerdo con unix.stackexchange.com/help/on-topic .
contramode
2
@mtraceur si crees que no es amplio, no estoy de acuerdo contigo. Hay mucho que contar sobre la diferencia entre compilador / intérprete / etc. e idiomas interpretados / compilados. Hay cevas que son fáciles de golpear. Además, la pregunta se ajusta mejor a "Informática" o "Programadores" SE.
MatthewRock

Respuestas:

23

Significa que los scripts de shell no se compilan, se interpretan: el shell interpreta los scripts de un comando a la vez y descubre cada vez cómo ejecutar cada comando. Eso tiene sentido para los scripts de shell, ya que de todos modos pasan la mayor parte de su tiempo ejecutando otros programas.

Los programas C, por otro lado, generalmente se compilan: antes de que se puedan ejecutar, un compilador los convierte a código de máquina en su totalidad, de una vez por todas. Ha habido intérpretes de C en el pasado (como el intérprete de C de HiSoft en el Atari ST) pero fueron muy inusuales. Hoy en día los compiladores de C son muy rápidos; TCC es tan rápido que puede usarlo para crear "scripts C", con un #!/usr/bin/tcc -runshebang, para que pueda crear programas C que se ejecuten de la misma manera que los scripts de shell (desde la perspectiva de los usuarios).

Algunos idiomas suelen tener un intérprete y un compilador: BASIC es un ejemplo que me viene a la mente.

También puede encontrar los llamados compiladores de script de shell, pero los que he visto son solo envoltorios ofuscantes: todavía usan un shell para interpretar realmente el script. Como señala mtraceur , sin duda sería posible un compilador de script de shell adecuado, pero no muy interesante.

Otra forma de pensar en esto es considerar que la capacidad de interpretación de scripts de un shell es una extensión de su capacidad de manejo de línea de comandos, lo que naturalmente conduce a un enfoque interpretado. C, por otro lado, fue diseñado para producir binarios independientes; Esto lleva a un enfoque compilado. Los lenguajes que generalmente se compilan tienden a generar también intérpretes, o al menos analizadores de línea de comandos (conocidos como REPL, bucles read-eval-print ; un shell es en sí mismo un REPL).

Stephen Kitt
fuente
1
Muchos "compiladores" para lenguajes de secuencias de comandos son simplemente envoltorios. Recuerdo un compilador BASIC que concatenaría el ejecutable del intérprete con un código fuente comprimido.
Dmitry Grigoryev
@DmitryGrigoryev ¡Puedo creerlo fácilmente! Para BASIC estaba pensando en compiladores como Turbo Basic. Creo que hubo algunos compiladores reales para archivos BAT de DOS, ¡pero podría estar equivocado! También existe el enfoque común de código p ...
Stephen Kitt
1
Tenga en cuenta que un compilador "verdadero" para el shell es completamente posible. Es que no es por lo general vale la pena el esfuerzo / complejidad, ya que una variante ingenua acaba de producir un programa que llama generalmente un montón de execve, open, close, read, write, y pipellamadas al sistema, intercalados con algunos getenv, setenvy las operaciones de HashMap interna / matriz (por variables no exportados ), etc. Bourne Shell y sus derivados tampoco son lenguajes de programación que se benefician tanto de los ajustes del compilador de bajo nivel como el reordenamiento de código, etc.
mtraceur
@StephenKitt, ¿te importaría si planteo una nueva pregunta relacionada con un comentario de tu respuesta? Aunque su respuesta explica mucho. pero también plantea una nueva pregunta para mí.
Mestizo
Claro que obviamente, abriría una nueva pregunta. Pensé que querrías que lo preguntara en el chat. porque se relaciona con tu respuesta.
Mestizo
6

Considere el siguiente programa:

2 Mars Bars
2 Milks
1 Bread
1 Corn Flakes

En el bashcamino, deambulas por la tienda buscando barras de Marte, finalmente las localizas, luego deambulas buscando leche, etc. Esto funciona porque estás ejecutando un programa complejo llamado "Comprador experimentado" que puede reconocer un pan cuando ves uno. y todas las otras complejidades de las compras. bashEs un programa bastante complejo.

Alternativamente, puede entregar su lista de compras a un compilador de compras. El compilador piensa por un momento y te da una nueva lista. Esta lista es LARGA , pero consta de instrucciones mucho más simples:

... lots of instructions on how to get to the store, get a shopping cart etc.
move west one aisle.
move north two sections.
move hand to shelf three.
grab object.
move hand to shopping cart.
release object.
... and so on and so forth.

Como puede ver, el compilador sabe exactamente dónde está todo en la tienda, por lo que no es necesaria toda la fase de "buscar cosas".

Este es un programa en sí mismo y no necesita "Comprador experimentado" para ejecutarse. Todo lo que necesita es un humano con "Sistema operativo humano básico".

Volviendo a los programas de computadora: bashes "Comprador experimentado" y puede tomar un script y simplemente hacerlo sin compilar nada. El compilador de CA produce un programa independiente que ya no necesita ayuda para ejecutarse.

Tanto los intérpretes como los compiladores tienen sus ventajas y desventajas.

Stig Hemmer
fuente
3
Buena analogía ... también explica por qué puede usar la misma lista de compras pero no el mismo código de máquina en una arquitectura diferente (¡sic!): Todo está en diferentes ubicaciones, etc. Sin embargo, es posible que necesite un "comprador experimentado" diferente. quien conoce los diferentes supermercados.
Peter - Restablece a Mónica el
6

Todo se reduce a la diferencia técnica entre cómo el programa que puede leer / escribir como humano se convierte en las instrucciones de la máquina que su computadora comprende, y las diferentes ventajas y desventajas de cada método es la razón por la cual algunos idiomas están escritos para necesitar compiladores , y algunos están escritos para ser interpretados.

Primero, la diferencia técnica

(Nota: estoy simplificando mucho aquí para abordar la pregunta. Para una comprensión más profunda, las notas técnicas al final de mi respuesta elaboran / refinan algunas de las simplificaciones aquí, y los comentarios sobre esta respuesta tienen algunas aclaraciones útiles y discusión también ..)

Básicamente, hay dos categorías generales de lenguajes de programación:

  1. Otro programa (el "compilador") lee su programa, determina qué pasos debe hacer su código y luego escribe un nuevo programa en código de máquina (el "lenguaje" que su computadora entiende) que realiza esos pasos.
  2. Otro programa (el "intérprete") lee su programa, determina qué pasos debe hacer su código y luego realiza esos pasos por sí mismo . No se crea un nuevo programa.

C está en la primera categoría (el compilador de C traduce el lenguaje C al código de máquina de su computadora : el código de máquina se guarda en un archivo, y luego cuando ejecuta ese código de máquina, hace lo que desea).

bash está en la segunda categoría (el intérprete bash lee el lenguaje bash y el intérprete bash hace lo que usted desea: por lo que no hay un "módulo compilador" per se, el intérprete interpreta y ejecuta, mientras que un compilador lee y traduce) .

Es posible que ya haya notado lo que esto significa:

Con C, realiza el paso de "interpretación" una vez , luego, cada vez que necesita ejecutar el programa, simplemente le dice a su computadora que ejecute el código de la máquina; su computadora puede ejecutarlo directamente sin tener que hacer ningún "pensamiento" adicional.

Con bash, debe hacer el paso de "interpretar" cada vez que ejecuta el programa: su computadora ejecuta el intérprete de bash, y el intérprete de bash hace un "pensamiento" adicional para descubrir qué necesita hacer para cada comando, cada vez .

Por lo tanto, los programas C requieren más CPU, memoria y tiempo para prepararse (el paso de compilación) pero menos tiempo y trabajo para ejecutarse. Los programas bash requieren menos CPU, memoria y tiempo para prepararse, pero más tiempo y trabajo para ejecutarse. Probablemente no note estas diferencias la mayoría de las veces porque las computadoras son muy rápidas hoy en día, pero sí hace una diferencia, y esa diferencia se suma cuando necesita ejecutar programas grandes o complicados, o muchos programas pequeños.

Además, debido a que los programas C se convierten en código de máquina (el "idioma nativo") de la computadora, no puede tomar un programa y copiarlo en otra computadora con un código de máquina diferente (por ejemplo, Intel de 64 bits en Intel 32 -bit, o de Intel a ARM o MIPS o lo que sea). Tienes que pasar el tiempo para compilarlo para ese otro lenguaje de máquina nuevamente . Pero un programa bash se puede mover a otra computadora que tenga instalado el intérprete bash, y funcionará bien.

Ahora el por qué parte de tu pregunta

Los creadores de C estaban escribiendo un sistema operativo y otros programas en hardware desde hace varias décadas, que estaba bastante limitado por los estándares modernos. Por varias razones, convertir los programas en el código de máquina de la computadora era la mejor manera de lograr ese objetivo para ellos en ese momento. Además, estaban haciendo el tipo de trabajo en el que era importante que el código que escribieran funcionara de manera eficiente .

Y los creadores del shell Bourne y bash querían lo contrario: querían escribir programas / comandos que pudieran ejecutarse de inmediato: en la línea de comandos, en una terminal, solo desea escribir una línea, un comando y tenerlo ejecutar. Y querían scripts que usted escribiera para trabajar en cualquier lugar donde tuviera instalado el intérprete / programa shell.

Conclusión

En resumen, no necesita un compilador para bash, pero necesita uno para C porque esos lenguajes se convierten en acciones informáticas reales de manera diferente, y esa forma diferente de hacerlo se eligió porque los lenguajes tenían objetivos diferentes.

Otros detalles técnicos / avanzados / notas

  1. En realidad, podría crear un intérprete de C o un compilador de bash. No hay nada que impida que eso sea posible: solo esos lenguajes fueron creados para diferentes propósitos. A menudo es más fácil reescribir el programa en otro idioma que escribir un buen intérprete o compilador para un lenguaje de programación complejo. Especialmente cuando esos idiomas tienen algo específico en lo que eran buenos, y fueron diseñados con una cierta forma de trabajar en primer lugar. C fue diseñado para ser compilado, por lo que le faltan muchos métodos abreviados convenientes que desearía en un shell interactivo, pero es muy bueno para expresar una manipulación de datos / memoria muy específica y de bajo nivel e interactuar con el sistema operativo , que son tareas que a menudo te encuentras haciendo cuando quieres escribir código compilado eficientemente. Mientras tanto, bash es muy bueno para ejecutar otros programas,

  2. Detalles más avanzados: en realidad, hay lenguajes de programación que son una combinación de ambos tipos (traducen el código fuente "la mayor parte del tiempo", para que puedan hacer la mayor parte de la interpretación / "pensar" una vez, y hacer solo un poquito de la interpretación / "pensamiento" más adelante). Java, Python y muchos otros lenguajes modernos son en realidad tales mezclas: intentan obtener algunos de los beneficios de portabilidad y / o desarrollo rápido de los lenguajes interpretados, y algo de la velocidad de los lenguajes compilados. Hay muchas formas posibles de combinar dichos enfoques, y los diferentes idiomas lo hacen de manera diferente. Si desea profundizar en este tema, puede leer sobre lenguajes de programación compilando en "bytecode" (que es como compilar en su propio "lenguaje de máquina" inventado

  3. Usted preguntó por el bit de ejecución: en realidad, el bit de ejecución sólo está allí para indicar al sistema operativo que está ese archivo permitido para ser ejecutado. Sospecho que la única razón por la que los scripts de bash funcionan para usted sin el permiso de ejecución es porque los está ejecutando desde dentro de un shell de bash. Normalmente, el sistema operativo, cuando se le pide que ejecute un archivo sin el bit de ejecución establecido, simplemente devolverá un error. Pero algunos shells como bash verán ese error y se encargarán de ejecutar el archivo de todos modos, básicamente emulando los pasos que normalmente tomaría el sistema operativo (busque la línea "#!" Al comienzo del archivo e intente ejecutar ese programa para interpretar el archivo, con un valor predeterminado de sí mismo o /bin/shsi no hay una línea "#!").

  4. A veces, un compilador ya está instalado en su sistema, y ​​a veces los IDE vienen con su propio compilador y / o ejecutan la compilación por usted. Esto puede hacer que un lenguaje compilado "se sienta" como un lenguaje no compilado para usar, pero la diferencia técnica sigue ahí.

  5. Un lenguaje "compilado" no necesariamente se compila en código máquina, y la compilación completa es un tema en sí mismo. Básicamente, el término se usa ampliamente: en realidad puede referirse a algunas cosas. En un sentido específico, un "compilador" es solo un traductor de un idioma (típicamente un lenguaje de "nivel superior" más fácil de usar para los humanos) a otro idioma (típicamente un lenguaje de "nivel inferior" que es más fácil de usar para las computadoras) a veces, pero en realidad no muy a menudo, este es el código de máquina). Además, a veces, cuando la gente dice "compilador", en realidad están hablando de varios programas que trabajan juntos (para un compilador C típico, en realidad son cuatro programas: el "preprocesador", el compilador en sí, el "ensamblador" y el " enlazador ").

mtraceur
fuente
No entiendo por qué esta pregunta fue rechazada. Es una respuesta impresionantemente completa a una pregunta que es amplia y no muy clara.
Anthony Geoghegan
El compilador traduce un idioma a otro. No tiene que compilarse en lenguaje máquina. Puede tener un compilador de bytecode. Compilador de Java a ASM, etc. Los creadores de C no querían la mayor potencia, querían el lenguaje que se adaptara a sus necesidades. C podría interpretarse hasta cierto punto. Bash podría compilarse: hay shc . El hecho de que el lenguaje se compile o se interprete o no, la mayoría de las veces, depende de las herramientas que utilice, no del lenguaje en sí, aunque se siguen algunas convenciones.
MatthewRock
@MatthewRock He agregado una nota técnica para abordar la compilación en algo que no necesariamente es lenguaje de máquina. Siento que mi primera nota técnica ya cubre el tema "C podría ser interpretado ... Bash podría compilarse". Tengo una idea de cómo abordar el problema de "los fabricantes de C no querían la mayoría de la potencia", aunque creo que está bastante claro que diseñaron el lenguaje para ser eficiente en el hardware que estaban utilizando en ese momento (el byte nulo- lo de as-string-terminator se debió en parte al hecho de que hacer eso le permite usar un registro menos al iterar en una cadena en esos, después de todo).
mtraceur
@MatthewRock (cont.) Creo que es justo decir que los creadores de C no querían exactamente el poder, pero sí querían abstraer el trabajo de escritura del sistema operativo, que especialmente en aquellos días era algo donde la eficiencia del código era valiosa. Y fue un trabajo que de otro modo habrían sido realizados en ensamblador. Entonces, crearon un lenguaje que se correlacionaba estrechamente con el código de máquina utilizado por las máquinas PDP para las que escribían su código, que al menos como efecto secundario, si no como un objetivo de diseño notable, se prestaba a la eficiencia en esa plataforma, incluso con ingenuos compiladores no optimizadores.
mtraceur
No lo harían en ensamblador. Tenían B . Las trampas en todas partes, y la pregunta no está en el tema aquí. En general, la respuesta probablemente respondería a la pregunta, pero todavía hay detalles que no son precisos.
MatthewRock
5

Los lenguajes de programación / scripting pueden compilarse o interpretarse.

Los ejecutables compilados son siempre más rápidos y se pueden detectar muchos errores antes de la ejecución.

Los lenguajes interpretados son generalmente más simples de escribir y adaptar son menos estrictos que los lenguajes compilados, y no requieren compilación, lo que los hace más fáciles de distribuir.

Julie Pelletier
fuente
1
En el script bash también da un error cuando se ejecuta. Pero no lo compilamos.
Mestizo
30
siempre es una palabra peligrosa ...
Radon Rosborough
3
CPython compilado optimizado es a menudo más lento que asm.js optimizado (un subconjunto de JavaScript). Por lo tanto, hay un ejemplo de que no es más rápido, y por lo tanto no es "siempre" más rápido. Sin embargo, generalmente es mucho, mucho más rápido.
wizzwizz4
2
Esto no responde la pregunta.
mathreadler
3
siempre más rápido es un reclamo audaz. Pero esto va demasiado en profundidad en la teoría (y definición) del compilador e intérprete.
Giacomo Catenazzi
3

Imagine que el inglés no es su idioma nativo (eso podría ser bastante fácil para usted si el inglés no es su idioma nativo).

Hay 3 formas de leer esto:

  1. (Interpretado) Mientras lees, traduce cada palabra cada vez que la veas
  2. (Optimizado-Interpretado) Encuentre frases comunes (como "su idioma nativo"), traduzca y escríbalas. Luego, traduzca cada palabra, excepto las frases que ya ha traducido
  3. (Compilado) Pídale a alguien más que traduzca la respuesta completa

Las computadoras tienen una especie de "idioma nativo": una combinación de instrucciones que comprende el procesador e instrucciones que comprende el sistema operativo (por ejemplo, Windows, Linux, OSX, etc.). Este lenguaje no es legible por los humanos.

Los lenguajes de secuencias de comandos, como Bash, generalmente se dividen en las categorías 1 y 2. Toman una línea a la vez, traducen esa línea y la ejecutan, luego pasan a la línea siguiente. En Mac y Linux, se instalan bastantes intérpretes diferentes por defecto para diferentes idiomas, como Bash, Python y Perl. En Windows, debe instalarlos usted mismo.

Muchos lenguajes de secuencias de comandos realizan un pequeño preprocesamiento: intente acelerar la ejecución compilando fragmentos de código que se ejecutarán con frecuencia o que de lo contrario ralentizarían la aplicación. Algunos de los términos que puede escuchar incluyen la compilación Ahead-of-time (AOT) o Just-in-time (JIT).

Por último, los lenguajes compilados, como C, traducen todo el programa antes de que pueda ejecutarlos. Esto tiene la ventaja de que la traducción se puede realizar en una máquina diferente a la ejecución, por lo que cuando le entrega el programa al usuario, aunque todavía puede haber errores, ya se pueden limpiar varios tipos de errores. Al igual que si le dieras esto a tu traductor, y menciono cómo garboola mizene resplunks, eso podría parecer un inglés válido para ti, pero el traductor puede decirte que estoy hablando tonterías. Cuando ejecuta un programa compilado, no necesita un intérprete, ya está en el idioma nativo de la computadora

Sin embargo, hay un inconveniente en los lenguajes compilados: mencioné que las computadoras tienen un idioma nativo, compuesto por características del hardware y del sistema operativo; bueno, si compila su programa en Windows, no esperará que el programa compilado se ejecute en una Mac Algunos idiomas evitan esto compilando un tipo de idioma intermedio, un poco como el inglés Pidgin, de esa manera, obtienes los beneficios de un idioma compilado, así como un pequeño aumento de velocidad, pero eso significa que necesitas agrupar un intérprete con su código (o use uno que ya esté instalado).

Por último, su IDE probablemente estaba compilando sus archivos por usted y podría informarle sobre errores antes de ejecutar el código. A veces, esta comprobación de errores puede ser más profunda que lo que hará el compilador. Un compilador a menudo solo verificará tanto como sea necesario para que pueda producir código nativo sensible. Un IDE a menudo ejecutará algunas comprobaciones adicionales y puede decirle, por ejemplo, si ha definido una variable dos veces, o si ha importado algo que no ha utilizado.

usuario208769
fuente
Esta respuesta es excelente, pero creo que la compilación dinámica utilizada por el "intérprete" de Perl es distinta de lo que normalmente se llama "JIT", por lo que probablemente sea mejor evitar ese término. JIT se usa comúnmente para referirse a la compilación justo a tiempo de un código de bytes ya compilado al código de máquina de destino, por ejemplo, JVM, .Net CLR.
IMSoP
@IMSoP Sí, los idiomas que compilan byte, y los idiomas que compilan JIT a partir de código de bytes, son de hecho cosas diferentes. Creo que vale la pena mencionarlo (pegué una breve referencia a él en las notas de pie de página de mi respuesta), pero la idea general de que JIT cae dentro de lo que creo que vale la pena tener es que es posible estar en un punto intermedio entre "compilado" e "interpretado", es decir, parcialmente compilado y parcialmente interpretado. Dicho esto, no estoy seguro de si eso es más valioso o confuso / distractor para alguien que aún no comprende la distinción entre compilado / interpretado, como el OP.
mtraceur
@mtraceur Para ser honesto, incluso me pierdo un poco en la distinción entre los modelos de, por ejemplo, PHP 7, Perl 5, .Net y Java. Quizás el mejor resumen para un principiante es "hay varias formas de mezclar compilación e interpretación, incluido el uso de una representación intermedia y compilar o volver a compilar fragmentos a medida que se ejecuta el programa".
IMSoP
@IMSoP Estoy de acuerdo. Ese es el enfoque que tomé en mi respuesta, y este comentario tuyo me dio una idea de cómo refinarlo aún más. Así que gracias.
mtraceur
@IMSoP JIT no solo ocurre en bytecode; por ejemplo, node.js tiene algunas características JIT. Pero estoy de acuerdo: los incluí todos bajo el banner "JIT" por simplicidad, ya que esto parecía una pregunta para principiantes. Lo estoy editando para usar un término más simple.
user208769
1

Mucha gente habla de interpretación vs compilación, pero creo que esto puede ser un poco engañoso si lo miras de cerca, ya que algunos lenguajes interpretados se compilan en un bytecode intermedio antes de la ejecución.

Al final, la verdadera razón por la cual los programas C deben compilarse en formato ejecutable es que la computadora necesita hacer mucho trabajo para transformar el código en un archivo fuente C en algo que pueda ejecutar, por lo que tiene sentido guardar el producto de todo lo que funciona en un archivo ejecutable, por lo que no necesita volver a hacerlo cada vez que quiera ejecutar su programa.

Por otro lado, el intérprete de Shell necesita hacer muy poco trabajo para convertir un script de shell en "operaciones de máquina". Básicamente solo necesita leer el script línea por línea, dividirlo en espacios en blanco, configurar algunas redirecciones de archivos y tuberías y luego hacer un fork + exec. Dado que la sobrecarga de analizar y procesar la entrada de texto de un script de shell es muy pequeña en comparación con el tiempo que lleva iniciar los procesos en el script de shell, sería excesivo compilar los scripts de shell en un formato de máquina intermedio en lugar de simplemente interpretar El código fuente directamente.

hugomg
fuente
+1, aunque me pregunto si esto no es "poner el carro delante del caballo": tal vez las conchas originales inicialmente estaban destinadas a ser interactivamente utilizables y, por lo tanto, lo suficientemente bajas como para no molestarse en compilar, y la relativa simplicidad era solo una decisión de diseño basada en eso?
mtraceur
Sí, esa es otra forma de ver esta pregunta :)
hugomg