|
Cuando aprendemos a programar, normalmente escogemos o nos imponen un determinado lenguaje de programación. Una de las primeras distinciones que es interesante conocer acerca de nuestro lenguaje es si el lenguaje se compila o se interpreta.
En este artículo intentaremos explicar las diferencias entre ambas formas de trabajar. Además, desde la aparición del lenguaje Java hace ya algunos años, ha tomado fuerza otro planteamiento muy interesante: el de las llamadas máquinas virtuales.
En este sistema de máquinas virtuales se realiza una compilación y posteriormente una interpretación. No es un concepto nuevo, pero sin duda, no ha tenido una aplicación práctica masiva hasta la llegada de la plataforma Java de Sun Microsystems a principios de los 90, y algo después con la plataforma .NET de Microsoft y su equivalente Mono para entornos *ix.
Antes de comentar cada técnica, hagamos hincapié en una serie de conceptos.
La confección de un programa se realiza escribiendo una serie de órdenes o instrucciones que siguen las normas de un lenguaje de programación. Estas órdenes las escribimos en ficheros de texto plano, utilizando algún editor de textos más o menos sencillo, o bien alguno de los editores especializados para programación. Incluso, algunos entornos de programación proporcionan sus propias herramientas específicas para escribir programas, con muchas ventajas para el programador... pero por muy compleja que sea la herramienta para confeccionar programas, por lo general, el programa siempre se reduce a una serie de instrucciones en un fichero de texto.
A estos ficheros de texto les llamamos fuente. (del inglés source. A menudo se utilizan expresiones como código fuente, ficheros fuente, etc.).
Sin embargo, sabemos que la CPU sólo entiende su propio lenguaje, que normalmente es extraordinariamente sencillo comparado el lenguaje de programación que estamos aprendiendo. El lenguaje de la CPU es el código máquina (del inglés machine code). El código máquina son secuencias binarias que la CPU ejecuta como instrucciones sencillas. Por supuesto, cada modelo de CPU tiene su propio código máquina, aunque a veces, varios modelos de CPU tienen lenguajes máquina compatibles. (Nota: mucha gente confunde el lenguaje ensamblador con el lenguaje máquina. Aunque lo trataremos en otro artículo, por el momento nos quedaremos con que no son lo mismo: el lenguaje ensamblador es un lenguaje de programación y el código máquina no. Ningún programador de aplicaciones pierde su tiempo y sus neuronas escribiendo secuencias de 1 y 0 directamente)
Los humanos expresamos la dinámica de un programa mediante un lenguaje de los llamados de "alto nivel". Estos son lenguajes como C#, Java, Visual Basic, Delphi, etc... casi cualquiera de los nombres que vd. conoce. Decimos que un lenguaje tiene un nivel más bajo cuanto más parecido es en su expresión al código máquina y al funcionamiento de la CPU, y de un nivel más alto cuanto más ajeno es al funcionamiento de la CPU y más se acerca a la forma de razonar humana.
Pues bien... si realizamos un programa escribiendo su fuente en un lenguaje de alto nivel y la CPU sólo es capaz de ejecutar órdenes en el lenguaje de más bajo nivel que existe (el código máquina), es obvio que es necesario realizar un proceso de traducción desde el lenguaje de alto nivel al código máquina.
Es necesario tener en cuenta que cada orden de un lenguaje de alto nivel (por ejemplo, una orden para imprimir "hola mundo" por la pantalla) se traduce en largas secuencias de instrucciones en código máquina (incluso varios miles). Lo que para nosotros es una sola orden, para la CPU supone muchos pequeños pasos extremadamente simples.
Tampoco podemos olvidarnos del sistema operativo. Los programas de aplicación se apoyan no sólo en la CPU, sino que necesitan de la participación del sistema operativo para realizar muchas de sus labores. Así pues, muchas veces los programas se hacen pensando que su destino va a ser una determinada CPU más un determinado sistema operativo. A menudo, incluso hay que incluir en esta lista otros programas como servidores de aplicaciones, sistemas gestores de bases de datos, librerías de código, etc. A este conjunto de requisitos necesarios para la ejecución de un programa se le suele llamar a menudo plataforma.
COMPILAR
(NOTA: nos referimos a la compilación tradicional, no a la compilación en un entorno de máquina virtual, como Java o .NET)
La compilación consiste en coger los ficheros fuente que conforman un programa y, línea por línea, traducir cada instrucción de alto nivel por varias instrucciones en código máquina que realicen lo que la instrucción de alto nivel expresa.
Si se repite esa traducción para todas las líneas del código fuente, obtendremos un conjunto de instrucciones máquina. Grabando esas instrucciones máquina en un fichero que contiene una estructura interna que un determinado sistema operativo es capaz de entender, obtenemos un fichero binario ejecutable, o simplemente, "un ejecutable".
El encargado de realizar esta traducción es un tipo de programa llamado compilador. En su funcionamiento más básico, un compilador acepta uno o más ficheros fuente y si no contienen errores sintácticos produce un fichero binario ejecutable, que un sistema operativo será capaz de cargar en memoria principal y pedir a la CPU que lo ejecute.
A menudo se compara a un compilador con un traductor de idiomas. Imagine un libro escrito en inglés. Para publicar su versión en español, una persona que conoce las reglas de los dos idiomas y dispone de la técnica para expresarse en ambos va leyendo el libro en inglés frase a frase y va escribiendo su equivalente en español.

INTERPRETAR
El proceso de interpretación es bastante diferente al de compilación, pero su resultado debería ser similar: la ejecución de un programa.
El encargado de hacer esto es un programa llamado intérprete. A diferencia del compilador, el intérprete no produce una traducción a código máquina. El intérprete intenta realizar "al vuelo" lo que se expresa en los ficheros fuente. El intérprete contiene en su interior miles de porciones de código máquina, que combinándolas adecuadamente pueden realizar las mismas tareas que expresa una orden escrita en el lenguaje de alto nivel.
Cuando un programa es interpretado, el proceso que se sigue es el siguiente: el intérprete obtiene una instrucción del fichero fuente y la realiza inmediatamente. Para ello, ejecuta en secuencia varias de esas porciones de código máquina que comentábamos antes, y que residen en el interior del intérprete. Cuando la CPU termina la ejecución de esa secuencia, el resultado es que la CPU habrá hecho lo que la línea de código fuente expresaba.
Repitiendo esta secuencia para todas las líneas, el intérprete realiza lo que los ficheros fuente expresan... es decir, ejecuta el programa.
A menudo, se compara al intérprete con un traductor simultáneo de idiomas. Por ejemplo, cuando vemos en la tele a alguna personalidad expresándose en otro idioma y un traductor simultáneo nos traduce lo que esta persona dice "al vuelo". Esta persona no espera a que el discurso de la personalidad concluya para traducirlo todo de golpe, sino que cada vez que este traductor ha entendido un concepto, lo traduce al español y lo expresa.
Diferencias entre compilar e interpretar
La opción de compilar o interpretar no está siempre disponible. Algunos lenguajes típicamente se compilan y otros típicamente se interpretan. En muy pocas ocasiones podemos optar por una u otra indistintamente.
Por ejemplo, programas escritos en lenguajes como C o Pascal prácticamente siempre se compilan, y otros como Perl o Python prácticamente siempre se interpretan.
En el siguiente cuadro se esquematizan algunas de las diferencias entre compilar e interpretar.
|
Compilar
|
Interpretar
|
|
-Genera un ejecutable
|
-No genera un ejecutable
|
|
-El proceso de traducción se realiza una sola vez
|
-El proceso de traducción se realiza en cada ejecución
|
|
-La ejecución es muy rápida debido a que el programa ya ha sido traducido a código máquina
|
-La ejecución es más lenta, ya que para cada línea del programa es necesario realizar la traducción
|
|
-El ejecutable va dirigido a una plataforma concreta (una CPU, un sistema operativo, y quizá alguna otra consideración), siendo prácticamente imposible portarlo a otra. En ocasiones, si existe un compilador para otra plataforma, se puede recompilar el programa, aunque normalmente esto plantea serias dificultades. Los programas que se van a compilar suelen estar muy ligados a la plataforma de destino.
|
-No hay ejecutable, así que si existe un intérprete para una plataforma concreta, el programa se podrá ejecutar en ambas. Típicamente, los programas interpretados son mucho más portables que los compilados, ya que suelen existir intérpretes del mismo lenguaje en distintas plataformas. Los programas que se van a interpretar no suelen ser muy dependientes de su plataforma de destino, siendo más portables.
|
|
-Los lenguajes compilados suelen proporcionar al programador mecanismos más potentes y flexibles, a costa de una mayor ligazón a la plataforma.
|
-Los lenguajes interpretados no suelen ser muy dependientes de la plataforma de destino, pero en contrapartida suelen ser menos flexibles y potentes que los compilados.
|
|
-Una vez compilado el programa, el código fuente no es necesario para ejecutarlo, así que puede permanecer en secreto si se desea.
|
-El código fuente es necesario en cada ejecución, así que no puede permanecer en secreto
|
|
-Los errores sintácticos se detectan durante la compilación. Si el fuente contiene errores sintácticos, el compilador no producirá un ejecutable.
|
-Los errores sintácticos se detectan durante la ejecución, ya que traducción y ejecución se van haciendo simultáneamente. Algún error sintáctico podría quedar enmascarado, si para una ejecución concreta no es necesario traducir la línea que lo contiene. (Algunos intérpretes son capaces de evitar esto)
|
|
-Un programa compilado puede, por error, afectar seriamente a la estabilidad de la plataforma, comprometiendo la ejecución de los otros procesos, por ejemplo, acaparando la CPU, la memoria o algún otro recurso, siendo a veces complicado para el sistema operativo interrumpir su ejecución.
|
-Un programa interpretado con un comportamiento torpe normalmente puede ser interrumpido sin dificultad, ya que su ejecución está bajo el control del intérprete, y no sólo del sistema operativo.
|
Por último, comentar que existe un tipo de compilador denominado compilador cruzado, que se trabaja en una plataforma y es capaz de construir ejecutables para otra. Por ejemplo: los programas para una pequeña PDA, no se programan ni se compilan utilizando ésta, sino que se utiliza un ordenador de sobremesa, mucho más cómodo para el desarrollador, que tiene otra CPU y otro sistema operativo. No obstante, se utiliza un compilador que genera ejecutables para la PDA, con su CPU y su sistema operativo.
LA MÁQUINA VIRTUAL
  
Hemos visto que los programas interpretados o compilados tienen distintas ventajas e inconvenientes. En un intento de combinar lo mejor de ambos mundos, durante la década de los 90 surge con fuerza el enfoque de máquina virtual. Los principales lenguajes abanderados de esta tecnología son, por un lado, el lenguaje Java de Sun Microsystems, y por otro, los lenguajes de la plataforma .NET de Microsoft: Visual Basic, C# y J#. También merece mención el lenguaje Delphi de la compañía Borland, que desde hace tiempo funciona para la plataforma .NET. Por supuesto, hay muchos otros.
La filosofía de la máquina virtual es la siguiente: el código fuente se compila, detectando los errores sintácticos, y se genera una especie de ejecutable, con un código máquina dirigido a una CPU imaginaria. A esta especie de código máquina se le denomina código intermedio, lenguaje intermedio, p-code, o byte-code (según quién nos lo cuente).
Como esa CPU imaginaria no existe, para poder ejecutar ese ejecutable, se construye un intérprete. Este intérprete es capaz de leer cada una de las instrucciones de código máquina imaginario y ejecutarlas en una CPU real. A este intérprete se le denomina máquina virtual.

¿Y para qué todo este montaje?
Pues esta pregunta puede responderse desde varios puntos de vista, pero se pude afirmar que este esquema aporta muchas de las ventajas de la compilación y la interpretación, deshaciéndose de algunos inconvenientes, pero principalmente se pueden recalcar dos:
- Portabilidad: El código intermedio ya está libre de errores sintácticos, y es un código muy sencillo (al estilo del código máquina). Si existe un intérprete para este código en distintas plataformas, el mismo código se puede ejecutar en cada una de ellas. Además, la construcción de este intérprete será relativamente sencilla y su ejecución más rápida, ya que no ha de comprobar la sintaxis.
- Estabilidad: El código intermedio no es ejecutado por una CPU real directamente, sino por una CPU virtual: la máquina virtual. Esto permite un mayor control sobre este código, facilitando la labor de impedir que un código descontrolado afecte a la estabilidad de la plataforma real.
Para entender algo mejor este concepto, podemos fijarnos, por ejemplo, en el lenguaje JAVA. Imaginemos que disponemos de dos ordenadores: uno de ellos tiene una CPU Pentium y un sistema operativo Windows. El otro tiene una CPU Sparc y un sistema operativo Solaris.
- En el primer ordenador instalamos un compilador de Java y una máquina virtual de Java específicos para Windows+Pentium.
- En el segundo hacemos lo mismo, pero con un compilador y máquina virtual específicos para Solaris+Sparc.
- Confeccionamos un programa sencillo (por ejemplo, que escriba "Hola Mundo" por la pantalla) escrito en Java en el primer ordenador y lo compilamos, generando un ejecutable intermedio. Si utilizamos la máquina virtual del primer ordenador para ejecutar ese código intermedio, comprobaremos que el programa escribe, en efecto "Hola Mundo" por la pantalla.
- Si cogemos ese ejecutable intermedio lo llevamos tal cual a la máquina con Solaris+Sparc, podremos utilizar la máquina virtual instalada allí para ejecutarlo, y comprobaremos que el resultado es exactamente el mismo: "Hola Mundo".
Este sistema es también utilizado por los juegos Java de los teléfonos móviles o las PDA. Distintas marcas de teléfonos móviles son capaces de ejecutar el mismo juego, aun cuando cada teléfono tiene una CPU y un sistema operativo distinto [Si, los teléfonos móviles tienen CPU y un rudimentario sistema operativo. Multitud de cacharros de hoy en día los tienen: reproductores de DVD, de MP3, cajeros automáticos, GPS, incluso algunos electrodomésticos]
Desde otros puntos de vista también podemos encontrar las ventajas de este enfoque.
Vamos a repasar la tabla anterior, pero añadiendo la máquina virtual.
|
Compilar
|
Interpretar
|
Máquina virtual
|
|
-Genera un ejecutable
|
-No genera un ejecutable
|
-Genera una especie de ejecutable, pero portable entre plataformas, dirigido a una CPU imaginaria.
|
|
-El proceso de traducción se realiza una sola vez
|
-El proceso de traducción se realiza en cada ejecución
|
-Se realiza una sola traducción a código intermedio, y una interpretación muy rápida del código intermedio en cada ejecución.
|
|
-La ejecución es muy rápida debido a que el programa ya ha sido traducido a código máquina
|
-La ejecución es más lenta, ya que para cada línea del programa es necesario realizar la traducción
|
-La ejecución no es tan rápida como en la compilación tradicional ni tan lenta como en la intepretación.
|
|
-El ejecutable va dirigido a una plataforma concreta (una CPU, un sistema operativo, y quizá alguna otra consideración), siendo prácticamente imposible portarlo a otra. En ocasiones, si existe un compilador para otra plataforma, se puede recompilar el programa, aunque normalmente esto plantea serias dificultades. Los programas que se van a compilar suelen estar muy ligados a la plataforma de destino.
|
-No hay ejecutable, así que si existe un intérprete para una plataforma concreta, el programa se podrá ejecutar en ambas. Típicamente, los programas interpretados son mucho más portables que los compilados, ya que suelen existir intérpretes del mismo lenguaje en distintas plataformas. Los programas que se van a interpretar no suelen ser muy dependientes de su plataforma de destino, siendo más portables.
|
-El ejecutable va dirigido a una CPU imaginaria. Se puede transportar a una plataforma para la cual exista una "máquina virtual" (el intérprete de código intermedio)
|
|
-Los lenguajes compilados suelen proporcionar al programador mecanismos más potentes y flexibles, a costa de una mayor ligazón a la plataforma.
|
-Los lenguajes interpretados no suelen ser muy dependientes de la plataforma de destino, pero en contrapartida suelen ser menos flexibles y potentes que los compilados.
|
-La plataforma de destino es virtual. Así pues, los programas son dependientes de esta plataforma virtual, que es emulada luego sobre plataformas reales por la "maquina virtual".
|
|
-Una vez compilado el programa, el código fuente no es necesario para ejecutarlo, así que puede permanecer en secreto si se desea.
|
-El código fuente es necesario en cada ejecución, así que no puede permanecer en secreto
|
-El código fuente no es necesario para la ejecución, sólo el código intermedio.
|
|
-Los errores sintácticos se detectan durante la compilación. Si el fuente contiene errores sintácticos, el compilador no producirá un ejecutable.
|
-Los errores sintácticos se detectan durante la ejecución, ya que traducción y ejecución se van haciendo simultáneamente. Algún error sintáctico podría quedar enmascarado, si para una ejecución concreta no es necesario traducir la línea que lo contiene. (Algunos intérpretes son capaces de evitar esto)
|
-Los errores sintácticos se detectan durante la compilación.
|
|
-Un programa compilado puede, por error, afectar seriamente a la estabilidad de la plataforma, comprometiendo la ejecución de los otros procesos, por ejemplo, acaparando la CPU, la memoria o algún otro recurso, siendo a veces complicado para el sistema operativo interrumpir su ejecución.
|
-Un programa interpretado con un comportamiento torpe normalmente puede ser interrumpido sin dificultad, ya que su ejecución está bajo el control del intérprete, y no sólo del sistema operativo.
|
-Un programa con un comportamiento torpe es ejecutado sobre la máquina virtual, que tiene un control absoluto sobre él, con lo que no se suele comprometer la estabilidad de la plataforma real.
|
En definitiva, compilación e interpretación son las opciones típicas para los lenguajes de programación más tradicionales, presentando cada una de ellas sus ventajas e inconvenientes. El enfoque más moderno es el de máquina virtual, en el que se realiza una compilación cuya plataforma de destino es una máquina imaginaria o virtual, y el ejecutable intermedio es posteriormente interpretado en cada ejecución. El mundo de la programación va poco a poco pero sin pausa adoptando cada vez más este enfoque, ya que presenta un buen compromiso entre portabilidad y estabilidad. Este enfoque es el adoptado por los lenguajes más modernos, como Java, Visual Basic, C#, J#, Delphi, y cada día surgen versiones de otros lenguajes que funcionan según este esquema, como SmallTalk, Python, Eiffel y un largo etcétera.
NOTAS:
- 1. El término "máquina virtual" se aplica en ocasiones a productos como VMWare o VirtualPC, que permiten simular el funcionamiento de un PC dentro de otro. Aunque se utilice el mismo término, el funcionamiento de este tipo de productos no es el que se describe en este artículo.
- 2. La plataforma Java, originalmente desarrollada por Sun Microsystems dispone de versiones para sistemas operativos Linux, Windows y Solaris, y para varias CPU. Es necesario mencionar que existen otras implementaciones de Java que no han sido desarrolladas por Sun.
- 3. La plataforma .NET, originalmente desarrollada por Microsoft dispone de versiones que en principio van orientadas sólo a sistemas operativos Windows. Es necesario mencionar que existen otras implementaciones de .NET que no han sido desarrolladas por Microsoft y que permiten el desarrollo y la ejecución en otros sistemas operativos. Es muy de destacar la plataforma Mono
, que permite la ejecución y el desarrollo de proyectos .NET en otros sistemas operativos distintos de Windows.
|