Procesadores de lenguaje de programación

P

Introducción

Hoy en día, la mayoría de los programas están escritos en un lenguaje de alto nivel como C, Java o Python. Estos lenguajes están diseñados más para personas que para máquinas, al ocultar al programador algunos detalles de hardware de una computadora específica.

En pocas palabras, los lenguajes de alto nivel simplifican el trabajo de decirle a una computadora qué hacer. Sin embargo, dado que las computadoras solo entienden las instrucciones en código de máquina (en forma de 1 y 0), no podemos comunicarnos adecuadamente con ellas sin algún tipo de traductor.

Esta es por eso que existen los procesadores de lenguaje.

El procesador de lenguaje es un sistema traductor especial que se utiliza para convertir un programa escrito en un lenguaje de alto nivel, que llamamos “código fuente”, en código máquina, al que llamamos “programa objeto” o “código objeto”.

Para diseñar un procesador de lenguaje, se necesita una descripción muy precisa del léxico y la sintaxis, así como la semántica de un lenguaje de alto nivel.

Hay tres tipos de procesadores de idioma:

  • Ensamblador
  • Interprete
  • Compilador

En las siguientes secciones, repasaremos cada uno de estos tipos de procesadores y discutiremos su propósito, diferencias, etc.

Lenguajes de ensamblaje y ensamblador

La mayoría de los lenguajes ensambladores son muy similares al código de máquina (por eso son específicos de la arquitectura de una computadora o sistema operativo), pero en lugar de usar números binarios para describir una instrucción, usa símbolos mnemónicos.

Cada símbolo mnemónico representa un código de operación o instrucción, y normalmente necesitamos varios de ellos en conjunto para hacer algo útil. Estas instrucciones se pueden usar para mover valores entre registros (en la arquitectura Intel86-64 este comando sería MOV), para realizar operaciones aritméticas básicas con valores como suma, resta, multiplicación y división (ADD, SUB, MUL, DIV), así como las operaciones lógicas básicas como desplazar un número hacia la izquierda o hacia la derecha o la negación (SHL, SHR, NEG). También puede usar saltos incondicionales y condicionales, lo cual es útil para implementar un bucle “for”, un bucle “while” o una instrucción “if” (JMP, JE, JLE…).

Por ejemplo, si el procesador interpreta el comando binario 10110 como “pasar de un registro a otro registro”, un lenguaje ensamblador lo reemplazaría con un comando, como MOV.

Cada registro también tiene un identificador binario, como 000. Esto también se puede reemplazar con un nombre más “humano”, como EAX, que es uno de los registros generales en x86.

Si, por ejemplo, quisiéramos mover un valor a un registro, el código de la máquina se vería así:

00001 000 00001010
  • 00001: Es el comando de movimiento
  • 000: Es el identificador del registro
  • 00001010: Es el valor que queremos mover

En un lenguaje ensamblador, esto se puede escribir como algo como:

MOV EAX, A
  • MOV es el comando de movimiento
  • EAX es el identificador del registro
  • A es el valor hexadecimal que queremos mover (10 en decimal)

Si quisiéramos escribir una expresión simple EAX = 7 + 4 - 2 en código de máquina, se vería así:

00001 000 00000111
00001 001 00000100
00010 000 001
00001 001 00000010
00011 000 001
  • 00001 es el comando “mover”
  • 00010 es el comando de “suma”
  • 00011 es el comando de “resta”
  • 000, 001 son los identificadores de los registros
  • 00000111, 00000100, 00000010 son los valores enteros que estamos usando en estas expresiones

En ensamblado, este grupo de números binarios se escribiría como:

MOV EAX, 7
MOV R8, 4
ADD EAX, R8
MOV R9, 2
SUB EAX, R9
  • MOV es el comando de movimiento
  • ADD es el comando de suma
  • SUB es el comando de resta
  • EAX, R8, R9 son los identificadores de los registros
  • 7, 4, 2: son los valores enteros que estamos usando en estas expresiones

Aunque todavía no es tan legible como un lenguaje de alto nivel, sigue siendo mucho más legible por humanos que el comando binario. Los componentes de hardware de la CPU y los registros son mucho más abstractos.

Esto hace que sea más fácil para un programador escribir código fuente, sin necesidad de manipular números para programar. La traducción a código objeto en lenguaje de máquina es simple y directa, realizada por un ensamblador.

Dado que el código fuente ya es bastante similar al código de máquina, no hay necesidad de compilar o interpretar el código, está ensamblado tal cual.

Idiomas interpretados y el intérprete

Cada programa tiene una fase de traducción y una fase de ejecución. En los lenguajes interpretados, estas dos fases están entrelazadas: las instrucciones escritas en un lenguaje de programación de alto nivel se ejecutan directamente sin convertirse previamente en código objeto o código máquina.

Ambas fases las realiza un Interprete – un procesador de lenguaje que traduce una sola declaración (línea de código), la ejecuta inmediatamente y luego pasa a la siguiente línea. Si se enfrenta a un error, un intérprete finaliza el proceso de traducción en esa línea y muestra un error. No puede pasar a la siguiente línea y ejecutarla a menos que se elimine el error anterior.

Se han utilizado intérpretes desde 1952 y su trabajo consistía en facilitar la programación dentro de las limitaciones de las computadoras en ese momento (por ejemplo, había mucho menos espacio de almacenamiento en la primera generación de computadoras que el que hay ahora). El primer lenguaje interpretado de alto nivel fue Lisp, implementado por primera vez en 1958 en una computadora IBM704.

Los lenguajes de programación interpretados más comunes hoy en día son Python, Perl y Ruby.

Lenguajes compilados y el compilador

A diferencia de los lenguajes de programación interpretados, la fase de traducción y la fase de ejecución en los lenguajes de programación compilados están completamente separadas, y la traducción la realiza un compilador.

El compilador es un procesador de lenguaje que lee el código fuente completo escrito en un lenguaje de alto nivel y lo traduce a un código objeto equivalente como un todo. Normalmente, este código de objeto se almacena en un archivo. Si hay algún error en el código fuente, el compilador lo especifica al final de la compilación, junto con las líneas en las que se encontraron los errores. Después de su eliminación, el código fuente se puede volver a compilar.

Los lenguajes de bajo nivel generalmente se compilan porque, al ser traducidos directamente al código de máquina, permiten al programador mucho más control sobre los componentes de hardware como la memoria o la CPU.

El primer lenguaje de programación compilado de alto nivel fue FORTRAN, creado en 1957 por un equipo dirigido por John Backus en IBM.

Los lenguajes compilados más comunes hoy en día son C ++, Rust y Haskell.

Idiomas de códigos de bytes

Los lenguajes de código de bytes, también llamados lenguajes de “código portátil” o “código p”, son el tipo de lenguajes de programación que se incluyen en categorías de lenguajes interpretados y compilados, ya que utilizan tanto la compilación como la interpretación al traducir y ejecutar el código.

Bytecode es, en pocas palabras, un código de programa que se ha compilado a partir del código fuente en un código de bajo nivel diseñado para un intérprete de software. Después de la compilación (desde el código fuente hasta el código de bytes), se puede compilar en código de máquina, que es reconocido por la CPU, o se puede ejecutar mediante una máquina virtual, que luego actúa como intérprete.

El código de bytes es universal y se puede transferir en el estado compilado a otros dispositivos (con todas las ventajas del código compilado). Luego, la CPU lo convierte en el código de máquina específico para el dispositivo. Dicho esto, puede compilar el código fuente una vez y ejecutarlo en todas partes, siempre que el dispositivo tenga otra capa, que se utiliza para convertir el código de bytes en código de máquina.

La máquina virtual más conocida para la interpretación de códigos de bytes es Java Virtual Machine (JVM), que es tan común que varios lenguajes tienen implementaciones construidas para ejecutarse en ella.

Crédito: ViralPatel

Cuando el programa se ejecuta por primera vez en un lenguaje de código de bytes, hay un retraso mientras el código se compila en código de bytes, pero la velocidad de ejecución aumenta significativamente en comparación con los lenguajes interpretativos estándar (ya que el código fuente está optimizado para el intérprete).

Una de las mayores ventajas de los lenguajes de código de bytes es su independencia de plataforma, que solía ser típica solo para los lenguajes interpretados, mientras que los programas son mucho más rápidos que los lenguajes interpretados normales en lo que respecta a la ejecución.

Otra cosa que vale la pena mencionar aquí es la compilación justo a tiempo (JIT). A diferencia de la compilación anticipada (AOT), el código se compila mientras se ejecuta. Esto esencialmente mejora la velocidad de compilación y utiliza los beneficios de rendimiento de la compilación con la flexibilidad de la interpretación.

Por otra parte, la compilación dinámica no siempre tiene que ser mejor / más rápida que la compilación estática; depende principalmente del tipo de proyecto en el que esté trabajando.

Los lenguajes insignia que se compilan en código de bytes son Java y C # y con ellos están los lenguajes como Clojure, Groovy, Kotlin y Scala.

Ventajas y desventajas: compilado frente a interpretado

Actuación

Dado que un compilador traduce un código fuente completo de un lenguaje de programación en código de máquina ejecutable para CPU, se necesita una gran cantidad de tiempo para analizar el código fuente, pero una vez que el análisis y la compilación están terminados, la ejecución general es mucho más rápida.

Por otro lado, el intérprete traduce el código fuente línea por línea, cada uno se ejecuta a medida que se traduce, lo que conduce a un análisis más rápido del código fuente, pero la ejecución es significativamente más lenta.

Depuración

La depuración es mucho más fácil cuando se trata de lenguajes de programación interpretados porque el código se va traduciendo hasta que se cumple el error, por lo que sabemos exactamente dónde está y es más fácil de arreglar.

Por el contrario, depurar en un lenguaje compilado es mucho más tedioso. Si un programa está escrito en un lenguaje compilado, debe compilarse manualmente, que es un paso adicional para ejecutar un programa. Puede que esto no parezca un problema, y ​​no lo es con los programas pequeños.

Tenga en cuenta que la compilación de proyectos masivos puede llevar decenas de minutos e incluso horas.

Además, el compilador genera el mensaje de error después de haber escaneado el código fuente en su totalidad, por lo que el error podría estar en cualquier parte del programa. Incluso si se especifica la línea de un error, después de cambiar el código fuente y corregirlo, necesitamos volver a compilarlo y solo entonces se puede ejecutar la versión mejorada. Esto puede no parecer un problema, y ​​no lo es con programas pequeños.

Tenga en cuenta que los proyectos masivos pueden tardar decenas de minutos y algunos incluso horas en compilarse. Afortunadamente, se pueden notar muchos errores antes de la compilación con la ayuda de IDE, pero no todos.

Código fuente vs código de objeto

Para lenguajes de programación interpretados, el código fuente es necesario para la ejecución. Esto significa que el código fuente de la aplicación está expuesto al usuario, como JavaScript está expuesto en el navegador.

Permitir que los usuarios lean completamente el código fuente puede permitir a los usuarios malintencionados manipular y encontrar lagunas en la lógica. Esto puede, hasta cierto punto, limitarse mediante la ofuscación de código, pero sigue siendo mucho más accesible que el código compilado.

Por otro lado, una vez que el programa escrito en un lenguaje de programación compilado se compila en un código objeto, se puede ejecutar un número infinito de veces y el código fuente ya no es necesario.

Por eso, cuando se pasa el programa a un usuario, basta con enviarle el código objeto, y no el código fuente, normalmente en forma de .exe archivo en Windows.

El código interpretado es más susceptible a los ataques de inyección de código y el hecho de que no se verifique el tipo nos presenta un nuevo conjunto de excepciones y errores de programación.

Conclusión

No existe una forma “mejor” de traducir el código fuente, y los lenguajes de programación tanto compilados como interpretados tienen sus ventajas y desventajas, como se mencionó anteriormente.

En muchos casos, la línea entre “compilado” e “interpretado” no está claramente definida cuando se trata de un lenguaje de programación más moderno, en realidad, no hay nada que le impida escribir un compilador para un lenguaje interpretado, por ejemplo.

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad