fbpx
Wikipedia

Compilador

En informática, un compilador es un tipo de traductor que transforma un programa entero de un lenguaje de programación (llamado código fuente) a otro.[1]​ Usualmente el lenguaje objetivo es código máquina, aunque también puede ser traducido a un código intermedio (bytecode) o a texto. A diferencia de los intérpretes, los compiladores reúnen diversos elementos o fragmentos en una misma unidad (un programa ejecutable o una biblioteca), que puede ser almacenada y reutilizada. Este proceso de traducción se conoce como compilación.[2]

Diagrama a bloques de la operación de un buen compilador.

La construcción de un compilador involucra la división del proceso en una serie de fases que variará con su complejidad. Generalmente estas fases se agrupan en dos tareas: el análisis del programa fuente y la síntesis del programa objeto.

  • Análisis: se trata de la comprobación de la corrección del programa fuente, según la definición del lenguaje en términos de teoría de lenguajes formales. Incluye las fases correspondientes al análisis léxico (que consiste en la descomposición del programa fuente en componentes léxicos), análisis sintáctico (agrupación de los componentes léxicos en frases gramaticales ) y análisis semántico (comprobación de la validez semántica de las sentencias aceptadas en la fase de análisis sintáctico).
  • Síntesis: su objetivo es la generación de la salida expresada en el lenguaje objeto y suele estar formado por una o varias combinaciones de fases de generación de código (normalmente se trata de código intermedio o de código objeto) y de optimización de código (en las que se busca obtener un programa objetivo lo más eficiente posible, según su complejidad computacional o complejidad de Kolmogórov: tiempo de ejecución, espacio durante ejecución, espacio para ser almacenado fuera de ejecución, etc).

Alternativamente, las fases descritas para las tareas de análisis y síntesis se pueden agrupar en:

  • Analizador o front-end: es la parte que analiza el código fuente, comprueba su validez, genera el árbol de derivación y rellena los valores de la tabla de símbolos. Esta parte suele ser independiente de la plataforma o sistema para el cual se vaya a compilar, y está compuesta por las fases comprendidas entre el análisis léxico y la generación de código intermedio.
  • Generador o back-end: es la parte que genera el código máquina, específico de una plataforma, a partir de los resultados de la fase de análisis, realizada por este generador.

Esta división permite que el mismo generador se utilice para crear el código máquina de varios lenguajes de programación distintos y que el mismo analizador que sirve para examinar el código fuente de un lenguaje de programación concreto sirva para producir código máquina en varias plataformas. Suele incluir la generación y optimización del código dependiente de la máquina.

Historia

En 1938, Konrad Zuse desarrolló la primera computadora digital electromecánica, denominada Z1 en Alemania, y posteriormente, en 1946, se desarrolló la primera computadora totalmente electrónica ENIAC, sucedida principalmente por la EDVAC (1951), primera computadora electrónica digital. En un principio, estas máquinas ejecutaban instrucciones consistentes en códigos numéricos que señalaban a los circuitos de la máquina los estados correspondientes a cada operación, lo que se denominó lenguaje máquina.

Pronto los primeros usuarios de estos ordenadores descubrieron la ventaja de escribir sus programas mediante claves más fáciles de recordar que esos códigos; al final, todas esas claves juntas se traducían manualmente a lenguaje máquina. Estas claves constituyen los llamados lenguajes ensambladores.

Pese a todo, el lenguaje ensamblador seguía siendo el de una máquina, pero más fácil de manejar (las instrucciones de máquina se reemplazan por mnemónicos. Los trabajos de investigación se orientaron hacia la creación de un lenguaje que expresara las distintas acciones a realizar de una manera lo más sencilla posible para una persona. El primer compilador fue escrito por Grace Hopper, en 1952 para el lenguaje de programación A-0. En 1950 John Backus dirigió una investigación en IBM sobre un lenguaje algebraico. En 1954 se empezó a desarrollar un lenguaje que permitía escribir fórmulas matemáticas de manera traducible por un ordenador; le llamaron FORTRAN (FORmulae TRANslator). Fue el primer lenguaje de alto nivel y se introdujo en 1957 para el uso de la computadora IBM modelo 704.

Surgió así por primera vez el concepto de un traductor como un programa que traducía un lenguaje a otro lenguaje. En el caso particular de que el lenguaje a traducir es un lenguaje de alto nivel y el lenguaje traducido de bajo nivel, se emplea el término compilador.

El trabajo de realizar un compilador fue complicado de realizar. El primer compilador de FORTRAN tardó 18 años-persona en realizarse y era muy sencillo. Este desarrollo de FORTRAN estaba muy influenciado por la máquina objeto en la que iba a ser implementado. Como un ejemplo de ello tenemos el hecho de que los espacios en blanco fuesen ignorados, debido a que el periférico que se utilizaba como entrada de programas (una lectora de tarjetas perforadas) no contaba correctamente los espacios en blanco.

El primer compilador autocontenido, es decir, capaz de compilar su propio código fuente fue el creado para Lisp por Hart y Levin en el MIT en 1962. Desde 1970 se ha convertido en una práctica común escribir el compilador en el mismo lenguaje que este compila, aunque PASCAL y C han sido alternativas muy usadas.

Crear un compilador autocontenido genera un problema llamado bootstrapping, es decir el primer compilador creado para un lenguaje tiene que o bien ser compilado por un compilador escrito en otro lenguaje o bien compilado al ejecutar el compilador en un intérprete.

Tipos de compiladores

Esta taxonomía de los tipos de compiladores no es excluyente, por lo que puede haber compiladores que se adscriban a varias categorías:

  • Compiladores cruzados: generan código para un sistema distinto del que están funcionando.
  • Compiladores optimizadores: realizan cambios en el código para mejorar su eficiencia, pero manteniendo la funcionalidad del programa original.
  • Compiladores de una sola pasada: generan el código máquina a partir de una única lectura del código fuente.
  • Compiladores de varias pasadas: necesitan leer el código fuente varias veces antes de poder producir el código máquina.
  • Compiladores JIT (just in time): forman parte de un intérprete y compilan partes del código según se necesitan.

En las primeras épocas de la informática, los compiladores eran considerados un software de los más complejos existentes.

Los primeros compiladores se realizaron programándolos directamente en lenguaje máquina o en ensamblador. Una vez que se dispone de un compilador, se pueden escribir nuevas versiones del compilador (u otros compiladores distintos) en el lenguaje que compila ese compilador.

Existen herramientas que facilitan la tarea de escribir compiladores o intérpretes informáticos. Estas herramientas permiten generar el esqueleto del analizador sintáctico a partir de una definición formal del lenguaje de partida, especificada normalmente mediante una gramática formal y barata, dejando únicamente al programador del compilador la tarea de programar las acciones semánticas asociadas.

Proceso de compilación

Es el proceso por el cual se traducen las instrucciones escritas en un determinado lenguaje de programación a lenguaje máquina. Además de un traductor, se pueden necesitar otros programas para crear un programa objeto ejecutable. Un programa fuente se puede dividir en módulos almacenados en archivos distintos. La tarea de reunir el programa fuente a menudo se confía a un programa distinto, llamado preprocesador. El preprocesador también puede expandir abreviaturas, llamadas a macros, a proposiciones del lenguaje fuente.

Normalmente la creación de un programa ejecutable (un típico archivo .exe para Windows o DOS) conlleva dos pasos. El primer paso se llama compilación (propiamente dicho) y traduce el código fuente escrito en un lenguaje de programación almacenado en un archivo a código en bajo nivel (normalmente en código objeto, no directamente a lenguaje máquina). El segundo paso se llama enlazado en el cual se enlaza el código de bajo nivel generado de todos los ficheros y subprogramas que se han mandado a compilar y se añade el código de las funciones que hay en las bibliotecas del compilador para que el ejecutable pueda comunicarse directamente con el sistema operativo, traduciendo así finalmente el código objeto a código máquina, y generando un módulo ejecutable.

Estos dos pasos se pueden hacer por separado, almacenando el resultado de la fase de compilación en archivos objetos (un típico.obj para Microsoft Windows, DOS o para Unix); para enlazarlos en fases posteriores, o crear directamente el ejecutable; con lo que la fase de compilación se almacena solo temporalmente. Un programa podría tener partes escritas en varios lenguajes (por ejemplo C, C++ y Asm), que se podrían compilar de forma independiente y luego enlazar juntas para formar un único módulo ejecutable.

Etapas del proceso

El proceso de traducción se compone internamente de varias etapas o fases, que realizan distintas operaciones lógicas. Es útil pensar en estas fases como en piezas separadas dentro del traductor, y pueden en realidad escribirse como operaciones codificadas separadamente aunque en la práctica a menudo se integren juntas.

Fase de análisis

Análisis léxico

El análisis léxico constituye la primera fase, aquí se lee el programa fuente de izquierda a derecha y se agrupa en componentes léxicos (tókenes), que son secuencias de caracteres que tienen un significado. Además, todos los espacios en blanco, líneas en blanco, comentarios y demás información innecesaria se elimina del programa fuente. También se comprueba que los símbolos del lenguaje (palabras clave, operadores, etc.) se han escrito correctamente.

Como la tarea que realiza el analizador léxico es un caso especial de coincidencia de patrones, se necesitan los métodos de especificación y reconocimiento de patrones, se usan principalmente los autómatas finitos que acepten expresiones regulares. Sin embargo, un analizador léxico también es la parte del traductor que maneja la entrada del código fuente, y puesto que esta entrada a menudo involucra un importante gasto de tiempo, el analizador léxico debe funcionar de manera tan eficiente como sea posible.

Análisis sintáctico

En esta fase los caracteres o componentes léxicos se agrupan jerárquicamente en frases gramaticales que el compilador utiliza para sintetizar la salida. Se comprueba si lo obtenido de la fase anterior es sintácticamente correcto (obedece a la gramática del lenguaje). Por lo general, las frases gramaticales del programa fuente se representan mediante un árbol de análisis sintáctico.

La estructura jerárquica de un programa normalmente se expresa utilizando reglas recursivas. Por ejemplo, se pueden dar las siguientes reglas como parte de la definición de expresiones:

  1. Un identificador puede ser una expresión.
  2. Un número puede ser una expresión.
  3. Si expresión1 y expresión2 son expresiones, entonces también lo son:
    • expresión1 + expresión2
    • expresión1 * expresión2
    • ( expresión1 )

Las reglas 1 y 2 son reglas básicas (no recursivas), en tanto que la regla 3 define expresiones en función de operadores aplicados a otras expresiones.

La división entre análisis léxico y análisis sintáctico es algo arbitraria. Un factor para determinar la división es si una construcción del lenguaje fuente es inherentemente recursiva o no. Las construcciones léxicas no requieren recursión, mientras que las construcciones sintácticas suelen requerirla. No se requiere recursión para reconocer los identificadores, que suelen ser cadenas de letras y dígitos que comienzan con una letra. Normalmente, se reconocen los identificadores por el simple examen del flujo de entrada, esperando hasta encontrar un carácter que no sea ni letra ni dígito, y agrupando después todas las letras y dígitos encontrados hasta ese punto en un componente léxico llamado identificador. Por otra parte, esta clase de análisis no es suficientemente poderoso para analizar expresiones o proposiciones. Por ejemplo, no podemos emparejar de manera apropiada los paréntesis de las expresiones, o las palabras begin y end en proposiciones sin imponer alguna clase de estructura jerárquica o de anidamiento a la entrada.

Análisis semántico

La fase de análisis semántico revisa el programa fuente para tratar de encontrar errores semánticos y reúne la información sobre los tipos para la fase posterior de generación de código. En ella se utiliza la estructura jerárquica determinada por la fase de análisis sintáctico para identificar los operadores y operandos de expresiones y proposiciones.

Un componente importante del análisis semántico es la verificación de tipos. Aquí, el compilador verifica si cada operador tiene operandos permitidos por la especificación del lenguaje fuente. Por ejemplo, las definiciones de muchos lenguajes de programación requieren que el compilador indique un error cada vez que se use un número real como índice de una matriz. Sin embargo, la especificación del lenguaje puede imponer restricciones a los operandos, por ejemplo, cuando un operador aritmético binario se aplica a un número entero y a un número real.[3]​ Revisa que los arreglos tengan definido el tamaño correcto.

Fase de síntesis

Consiste en generar el código objeto equivalente al programa fuente. Solo se genera código objeto cuando el programa fuente está libre de errores de análisis, lo cual no quiere decir que el programa se ejecute correctamente, ya que un programa puede tener errores de concepto o expresiones mal calculadas. Por lo general el código objeto es código de máquina relocalizable o código ensamblador. Las posiciones de memoria se seleccionan para cada una de las variables usadas por el programa. Después, cada una de las instrucciones intermedias se traduce a una secuencia de instrucciones de máquina que ejecuta la misma tarea. Un aspecto decisivo es la asignación de variables a registros.

Generación de código intermedio

Después de los análisis sintáctico y semántico, algunos compiladores generan una representación intermedia explícita del programa fuente. Se puede considerar esta representación intermedia como un programa para una máquina abstracta. Esta representación intermedia debe tener dos propiedades importantes; debe ser fácil de producir y fácil de traducir al programa objeto.

La representación intermedia puede tener diversas formas. Existe una forma intermedia llamada «código de tres direcciones» que es como el lenguaje ensamblador de una máquina en la que cada posición de memoria puede actuar como un registro. El código de tres direcciones consiste en una secuencia de instrucciones, cada una de las cuales tiene como máximo tres operandos. Esta representación intermedia tiene varias propiedades:

  • Primera: cada instrucción de tres direcciones tiene a lo sumo un operador, además de la asignación, por tanto, cuando se generan estas instrucciones, el traductor tiene que decidir el orden en que deben efectuarse las operaciones.
  • Segunda: el traductor debe generar un nombre temporal para guardar los valores calculados por cada instrucción.
  • Tercera: algunas instrucciones de «tres direcciones» tienen menos de tres operandos, por ejemplo, la asignación.

Optimización de código

La fase de optimización de código consiste en mejorar el código intermedio, de modo que resulte un código máquina más rápido de ejecutar. Esta fase de la etapa de síntesis es posible sobre todo si el traductor es un compilador (difícilmente un intérprete puede optimizar el código objeto). Hay mucha variación en la cantidad de optimización de código que ejecutan los distintos compiladores. En los que hacen mucha optimización, llamados «compiladores optimizadores», una parte significativa del tiempo del compilador se ocupa en esta fase. Sin embargo, hay optimizaciones sencillas que mejoran sensiblemente el tiempo de ejecución del programa objeto sin retardar demasiado la compilación.[3]

Estructura de datos principales

La interacción entre los algoritmos utilizados por las fases del compilador y las estructuras de datos que soportan estas fases es, naturalmente, muy fuerte. El escritor del compilador se esfuerza por implementar estos algoritmos de una manera tan eficaz como sea posible, sin aumentar demasiado la complejidad. De manera ideal, un compilador debería poder compilar un programa en un tiempo proporcional al tamaño del mismo.

Componentes léxicos o tókenes

Cuando un analizador léxico reúne los caracteres en un token, generalmente representa el token de manera simbólica, es decir, como un valor de un tipo de datos enumerado que representa el conjunto de tokens del lenguaje fuente. En ocasiones también es necesario mantener la cadena de caracteres misma u otra información derivada de ella, tal como el nombre asociado con un token identificador o el valor de un token de número.

En la mayoría de los lenguajes el analizador léxico solo necesita generar un token a la vez. En este caso se puede utilizar una variable global simple para mantener la información del token. En otros casos (cuyo ejemplo más notable es FORTRAN), puede ser necesario un arreglo (o vector) de tókenes.

Árbol sintáctico

Si el analizador sintáctico genera un árbol sintáctico, por lo regular se construye como una estructura estándar basada en un puntero que se asigna de manera dinámica a medida que se efectúa el análisis sintáctico. El árbol entero puede entonces conservarse como una variable simple que apunta al nodo raíz. Cada nodo en la estructura es un registro cuyos campos representan la información recolectada tanto por el analizador sintáctico como, posteriormente, por el analizador semántico. Por ejemplo, el tipo de datos de una expresión puede conservarse como un campo en el nodo del árbol sintáctico para la expresión.

En ocasiones, para ahorrar espacio, estos campos se asignan de manera dinámica, o se almacenan en otras estructuras de datos, tales como la tabla de símbolos, que permiten una asignación y desasignación selectivas. En realidad, cada nodo del árbol sintáctico por sí mismo puede requerir de atributos diferentes para ser almacenado, de acuerdo con la clase de estructura del lenguaje que represente. En este caso, cada nodo en el árbol sintáctico puede estar representado por un registro variable, con cada clase de nodo conteniendo solamente la información necesaria para ese caso.

Tabla de símbolos

Esta estructura de datos mantiene la información asociada con los identificadores: funciones, variables, constantes y tipos de datos. La tabla de símbolos interactúa con casi todas las fases del compilador: el analizador léxico, el analizador sintáctico o el analizador semántico pueden introducir identificadores dentro de la tabla; el analizador semántico agregará tipos de datos y otra información; y las fases de optimización y generación de código utilizarán la información proporcionada por la tabla de símbolos para efectuar selecciones apropiadas de código objeto.

Puesto que la tabla de símbolos tendrá solicitudes de acceso con tanta frecuencia, las operaciones de inserción, eliminación y acceso necesitan ser eficientes, preferiblemente operaciones de tiempo constante. Una estructura de datos estándar para este propósito es la tabla de dispersión o de cálculo de dirección, aunque también se pueden utilizar diversas estructuras de árbol. En ocasiones se utilizan varias tablas y se mantienen en una lista o pila.

Tabla de literales

La búsqueda y la inserción rápida son esenciales también para la tabla de literales, la cual almacena constantes y cadenas utilizadas en el programa. Sin embargo, una tabla de literales necesita impedir las eliminaciones porque sus datos se aplican globalmente al programa y una constante o cadena aparecerá solo una vez en esta tabla. La tabla de literales es importante en la reducción del tamaño de un programa en la memoria al permitir la reutilización de constantes y cadenas. También es necesaria para que el generador de código construya direcciones simbólicas para las literales y para introducir definiciones de datos en el archivo de código objeto.

Código intermedio

De acuerdo con la clase de código intermedio (por ejemplo, código de tres direcciones o código P) y de las clases de optimizaciones realizadas, este código puede conservarse como un arreglo de cadenas de texto, un archivo de texto temporal o bien una lista de estructuras ligadas. En los compiladores que realizan optimizaciones complejas debe ponerse particular atención a la selección de representaciones que permitan una fácil reorganización.

Generación de código intermedio

Después de los análisis sintáctico y semántico, algunos compiladores generan una representación intermedia explícita del programa fuente. Se puede considerar esta representación intermedia como un programa para una máquina abstracta. Esta representación intermedia debe tener dos propiedades importantes; debe ser fácil de producir y fácil de traducir al programa objeto.

La representación intermedia puede tener diversas formas. Existe una forma intermedia llamada «código de tres direcciones», que es como el lenguaje ensamblador para una máquina en la que cada posición de memoria puede actuar como un registro. El código de tres direcciones consiste en una secuencia de instrucciones, cada una de las cuales tiene como máximo tres operandos. El programa fuente de (1) puede aparecer en código de tres direcciones como

 temp1 := entreal(60) temp2 := id3 * temp1 ===> (2) temp3 := id2 + temp2 id1 := temp3 

Esta representación intermedia tiene varias propiedades. Primera, cada instrucción de tres direcciones tiene a lo sumo un operador, además de la asignación. Por tanto, cuando se generan esas instrucciones el compilador tiene que decidir el orden en que deben efectuarse, las operaciones; la multiplicación precede a la adición al programa fuente de. Segunda, el compilador debe generar un nombre temporal para guardar los valores calculados por cada instrucción. Tercera, algunas instrucciones de «tres direcciones» tienen menos de tres operadores, por ejemplo la primera y la última instrucciones de asignación.

Optimización de código

La fase de optimización de código trata de mejorar el código intermedio de modo que resulte un código de máquina más rápido de ejecutar. Algunas optimizaciones son triviales. Por ejemplo, un algoritmo natural genera el código intermedio (2) utilizando una instrucción para cada operador de la representación del árbol después del análisis semántico, aunque hay una forma mejor de realizar los mismos cálculos usando las dos instrucciones

temp1 := id3 * 60.0 ===> (3) id1 := id2 + temp1 

Este sencillo algoritmo no tiene nada de malo, puesto que el problema se puede solucionar en la fase de optimización de código. Esto es, el compilador puede deducir que la conversión de 60 de entero a real se puede hacer de una vez por todas en el momento de la compilación, de modo que la operación "entreal( )" se puede eliminar. Además, temp3 se usa solo una vez, para transmitir su valor a id1. Entonces resulta seguro sustituir a id1 por temp3, a partir de lo cual la última proposición de (2) no se necesita y se obtiene el código de (3).

Hay muchas variaciones en la cantidad de optimización de código que ejecutan los distintos compiladores. En lo que hacen mucha optimización llamados «compiladores optimizadores», una parte significativa del tiempo del compilador se ocupa en esta fase. Sin embargo, hay optimizaciones sencillas que mejoran sensiblemente el tiempo de ejecución del programa objeto sin retardar demasiado la compilación.

Archivos temporales

Al principio las computadoras no tenían la suficiente memoria para guardar un programa completo durante la compilación. Este problema se resolvió mediante el uso de archivos temporales para mantener los productos de los pasos intermedios durante la traducción o bien al compilar «al vuelo», es decir, manteniendo solo la información suficiente de las partes anteriores del programa fuente que permita proceder a la traducción.

Las limitaciones de memoria son ahora un problema mucho menor, y es posible requerir que una unidad de compilación entera se mantenga en memoria, en especial si se dispone de la compilación por separado en el lenguaje. Con todo, los compiladores ocasionalmente encuentran útil generar archivos intermedios durante alguna de las etapas del procesamiento. Algo típico de estos es la necesidad de direcciones de corrección hacia atrás durante la generación de código.

Véase también

Referencias

  1. Clocksin, William (1997). Clause and effect. Springer-Verlag. p. 93. ISBN 978-3-540-62971-9. 
  2. Laborda, Javier; Josep Galimany, Rosa María Pena, Antoni Gual (1985). «Software». Biblioteca práctica de la computación. Barcelona: Ediciones Océano-Éxito, S.A. 
  3. Aho, Alfred V.; Ravi Sethi, Jeffrey D. Ullman (2008). «Introducción a la Compilación». Compiladores: Principios, técnicas y prácticas. México: Addison Wesley. 

Enlaces externos

  •   Wikcionario tiene definiciones y otra información sobre compilador.
  • Let's Build a Compiler. Tutorial de Jack W. Crenshaw sobre cómo hacer un compilador
  • Java a tope: Traductores y Compiladores con Lex/Yacc, JFlex/Cup y JavaCC. Libro básico sobre compiladores
  •   Datos: Q47506
  •   Multimedia: Compilers

compilador, este, artículo, sección, necesita, referencias, aparezcan, publicación, acreditada, este, aviso, puesto, diciembre, 2017, compilación, redirige, aquí, para, otras, acepciones, véase, recopilación, informática, compilador, tipo, traductor, transform. Este articulo o seccion necesita referencias que aparezcan en una publicacion acreditada Este aviso fue puesto el 22 de diciembre de 2017 Compilacion redirige aqui Para otras acepciones vease recopilacion En informatica un compilador es un tipo de traductor que transforma un programa entero de un lenguaje de programacion llamado codigo fuente a otro 1 Usualmente el lenguaje objetivo es codigo maquina aunque tambien puede ser traducido a un codigo intermedio bytecode o a texto A diferencia de los interpretes los compiladores reunen diversos elementos o fragmentos en una misma unidad un programa ejecutable o una biblioteca que puede ser almacenada y reutilizada Este proceso de traduccion se conoce como compilacion 2 Diagrama a bloques de la operacion de un buen compilador La construccion de un compilador involucra la division del proceso en una serie de fases que variara con su complejidad Generalmente estas fases se agrupan en dos tareas el analisis del programa fuente y la sintesis del programa objeto Analisis se trata de la comprobacion de la correccion del programa fuente segun la definicion del lenguaje en terminos de teoria de lenguajes formales Incluye las fases correspondientes al analisis lexico que consiste en la descomposicion del programa fuente en componentes lexicos analisis sintactico agrupacion de los componentes lexicos en frases gramaticales y analisis semantico comprobacion de la validez semantica de las sentencias aceptadas en la fase de analisis sintactico Sintesis su objetivo es la generacion de la salida expresada en el lenguaje objeto y suele estar formado por una o varias combinaciones de fases de generacion de codigo normalmente se trata de codigo intermedio o de codigo objeto y de optimizacion de codigo en las que se busca obtener un programa objetivo lo mas eficiente posible segun su complejidad computacional o complejidad de Kolmogorov tiempo de ejecucion espacio durante ejecucion espacio para ser almacenado fuera de ejecucion etc Alternativamente las fases descritas para las tareas de analisis y sintesis se pueden agrupar en Analizador o front end es la parte que analiza el codigo fuente comprueba su validez genera el arbol de derivacion y rellena los valores de la tabla de simbolos Esta parte suele ser independiente de la plataforma o sistema para el cual se vaya a compilar y esta compuesta por las fases comprendidas entre el analisis lexico y la generacion de codigo intermedio Generador o back end es la parte que genera el codigo maquina especifico de una plataforma a partir de los resultados de la fase de analisis realizada por este generador Esta division permite que el mismo generador se utilice para crear el codigo maquina de varios lenguajes de programacion distintos y que el mismo analizador que sirve para examinar el codigo fuente de un lenguaje de programacion concreto sirva para producir codigo maquina en varias plataformas Suele incluir la generacion y optimizacion del codigo dependiente de la maquina Indice 1 Historia 2 Tipos de compiladores 3 Proceso de compilacion 4 Etapas del proceso 4 1 Fase de analisis 4 1 1 Analisis lexico 4 1 2 Analisis sintactico 4 1 3 Analisis semantico 4 2 Fase de sintesis 4 2 1 Generacion de codigo intermedio 4 3 Optimizacion de codigo 5 Estructura de datos principales 5 1 Componentes lexicos o tokenes 5 2 Arbol sintactico 5 3 Tabla de simbolos 5 4 Tabla de literales 5 5 Codigo intermedio 5 6 Archivos temporales 6 Vease tambien 7 Referencias 8 Enlaces externosHistoria EditarArticulo principal Historia de la construccion de los compiladores En 1938 Konrad Zuse desarrollo la primera computadora digital electromecanica denominada Z1 en Alemania y posteriormente en 1946 se desarrollo la primera computadora totalmente electronica ENIAC sucedida principalmente por la EDVAC 1951 primera computadora electronica digital En un principio estas maquinas ejecutaban instrucciones consistentes en codigos numericos que senalaban a los circuitos de la maquina los estados correspondientes a cada operacion lo que se denomino lenguaje maquina Pronto los primeros usuarios de estos ordenadores descubrieron la ventaja de escribir sus programas mediante claves mas faciles de recordar que esos codigos al final todas esas claves juntas se traducian manualmente a lenguaje maquina Estas claves constituyen los llamados lenguajes ensambladores Pese a todo el lenguaje ensamblador seguia siendo el de una maquina pero mas facil de manejar las instrucciones de maquina se reemplazan por mnemonicos Los trabajos de investigacion se orientaron hacia la creacion de un lenguaje que expresara las distintas acciones a realizar de una manera lo mas sencilla posible para una persona El primer compilador fue escrito por Grace Hopper en 1952 para el lenguaje de programacion A 0 En 1950 John Backus dirigio una investigacion en IBM sobre un lenguaje algebraico En 1954 se empezo a desarrollar un lenguaje que permitia escribir formulas matematicas de manera traducible por un ordenador le llamaron FORTRAN FORmulae TRANslator Fue el primer lenguaje de alto nivel y se introdujo en 1957 para el uso de la computadora IBM modelo 704 Surgio asi por primera vez el concepto de un traductor como un programa que traducia un lenguaje a otro lenguaje En el caso particular de que el lenguaje a traducir es un lenguaje de alto nivel y el lenguaje traducido de bajo nivel se emplea el termino compilador El trabajo de realizar un compilador fue complicado de realizar El primer compilador de FORTRAN tardo 18 anos persona en realizarse y era muy sencillo Este desarrollo de FORTRAN estaba muy influenciado por la maquina objeto en la que iba a ser implementado Como un ejemplo de ello tenemos el hecho de que los espacios en blanco fuesen ignorados debido a que el periferico que se utilizaba como entrada de programas una lectora de tarjetas perforadas no contaba correctamente los espacios en blanco El primer compilador autocontenido es decir capaz de compilar su propio codigo fuente fue el creado para Lisp por Hart y Levin en el MIT en 1962 Desde 1970 se ha convertido en una practica comun escribir el compilador en el mismo lenguaje que este compila aunque PASCAL y C han sido alternativas muy usadas Crear un compilador autocontenido genera un problema llamado bootstrapping es decir el primer compilador creado para un lenguaje tiene que o bien ser compilado por un compilador escrito en otro lenguaje o bien compilado al ejecutar el compilador en un interprete Tipos de compiladores EditarEsta taxonomia de los tipos de compiladores no es excluyente por lo que puede haber compiladores que se adscriban a varias categorias Compiladores cruzados generan codigo para un sistema distinto del que estan funcionando Compiladores optimizadores realizan cambios en el codigo para mejorar su eficiencia pero manteniendo la funcionalidad del programa original Compiladores de una sola pasada generan el codigo maquina a partir de una unica lectura del codigo fuente Compiladores de varias pasadas necesitan leer el codigo fuente varias veces antes de poder producir el codigo maquina Compiladores JIT just in time forman parte de un interprete y compilan partes del codigo segun se necesitan En las primeras epocas de la informatica los compiladores eran considerados un software de los mas complejos existentes Los primeros compiladores se realizaron programandolos directamente en lenguaje maquina o en ensamblador Una vez que se dispone de un compilador se pueden escribir nuevas versiones del compilador u otros compiladores distintos en el lenguaje que compila ese compilador Existen herramientas que facilitan la tarea de escribir compiladores o interpretes informaticos Estas herramientas permiten generar el esqueleto del analizador sintactico a partir de una definicion formal del lenguaje de partida especificada normalmente mediante una gramatica formal y barata dejando unicamente al programador del compilador la tarea de programar las acciones semanticas asociadas Proceso de compilacion EditarEs el proceso por el cual se traducen las instrucciones escritas en un determinado lenguaje de programacion a lenguaje maquina Ademas de un traductor se pueden necesitar otros programas para crear un programa objeto ejecutable Un programa fuente se puede dividir en modulos almacenados en archivos distintos La tarea de reunir el programa fuente a menudo se confia a un programa distinto llamado preprocesador El preprocesador tambien puede expandir abreviaturas llamadas a macros a proposiciones del lenguaje fuente Normalmente la creacion de un programa ejecutable un tipico archivo exe para Windows o DOS conlleva dos pasos El primer paso se llama compilacion propiamente dicho y traduce el codigo fuente escrito en un lenguaje de programacion almacenado en un archivo a codigo en bajo nivel normalmente en codigo objeto no directamente a lenguaje maquina El segundo paso se llama enlazado en el cual se enlaza el codigo de bajo nivel generado de todos los ficheros y subprogramas que se han mandado a compilar y se anade el codigo de las funciones que hay en las bibliotecas del compilador para que el ejecutable pueda comunicarse directamente con el sistema operativo traduciendo asi finalmente el codigo objeto a codigo maquina y generando un modulo ejecutable Estos dos pasos se pueden hacer por separado almacenando el resultado de la fase de compilacion en archivos objetos un tipico obj para Microsoft Windows DOS o para Unix para enlazarlos en fases posteriores o crear directamente el ejecutable con lo que la fase de compilacion se almacena solo temporalmente Un programa podria tener partes escritas en varios lenguajes por ejemplo C C y Asm que se podrian compilar de forma independiente y luego enlazar juntas para formar un unico modulo ejecutable Etapas del proceso EditarEl proceso de traduccion se compone internamente de varias etapas o fases que realizan distintas operaciones logicas Es util pensar en estas fases como en piezas separadas dentro del traductor y pueden en realidad escribirse como operaciones codificadas separadamente aunque en la practica a menudo se integren juntas Fase de analisis Editar Analisis lexico Editar Articulo principal Analizador lexico El analisis lexico constituye la primera fase aqui se lee el programa fuente de izquierda a derecha y se agrupa en componentes lexicos tokenes que son secuencias de caracteres que tienen un significado Ademas todos los espacios en blanco lineas en blanco comentarios y demas informacion innecesaria se elimina del programa fuente Tambien se comprueba que los simbolos del lenguaje palabras clave operadores etc se han escrito correctamente Como la tarea que realiza el analizador lexico es un caso especial de coincidencia de patrones se necesitan los metodos de especificacion y reconocimiento de patrones se usan principalmente los automatas finitos que acepten expresiones regulares Sin embargo un analizador lexico tambien es la parte del traductor que maneja la entrada del codigo fuente y puesto que esta entrada a menudo involucra un importante gasto de tiempo el analizador lexico debe funcionar de manera tan eficiente como sea posible Analisis sintactico Editar Articulo principal Analizador sintactico En esta fase los caracteres o componentes lexicos se agrupan jerarquicamente en frases gramaticales que el compilador utiliza para sintetizar la salida Se comprueba si lo obtenido de la fase anterior es sintacticamente correcto obedece a la gramatica del lenguaje Por lo general las frases gramaticales del programa fuente se representan mediante un arbol de analisis sintactico La estructura jerarquica de un programa normalmente se expresa utilizando reglas recursivas Por ejemplo se pueden dar las siguientes reglas como parte de la definicion de expresiones Un identificador puede ser una expresion Un numero puede ser una expresion Si expresion1 y expresion2 son expresiones entonces tambien lo son expresion1 expresion2 expresion1 expresion2 expresion1 Las reglas 1 y 2 son reglas basicas no recursivas en tanto que la regla 3 define expresiones en funcion de operadores aplicados a otras expresiones La division entre analisis lexico y analisis sintactico es algo arbitraria Un factor para determinar la division es si una construccion del lenguaje fuente es inherentemente recursiva o no Las construcciones lexicas no requieren recursion mientras que las construcciones sintacticas suelen requerirla No se requiere recursion para reconocer los identificadores que suelen ser cadenas de letras y digitos que comienzan con una letra Normalmente se reconocen los identificadores por el simple examen del flujo de entrada esperando hasta encontrar un caracter que no sea ni letra ni digito y agrupando despues todas las letras y digitos encontrados hasta ese punto en un componente lexico llamado identificador Por otra parte esta clase de analisis no es suficientemente poderoso para analizar expresiones o proposiciones Por ejemplo no podemos emparejar de manera apropiada los parentesis de las expresiones o las palabras begin y end en proposiciones sin imponer alguna clase de estructura jerarquica o de anidamiento a la entrada Analisis semantico Editar La fase de analisis semantico revisa el programa fuente para tratar de encontrar errores semanticos y reune la informacion sobre los tipos para la fase posterior de generacion de codigo En ella se utiliza la estructura jerarquica determinada por la fase de analisis sintactico para identificar los operadores y operandos de expresiones y proposiciones Un componente importante del analisis semantico es la verificacion de tipos Aqui el compilador verifica si cada operador tiene operandos permitidos por la especificacion del lenguaje fuente Por ejemplo las definiciones de muchos lenguajes de programacion requieren que el compilador indique un error cada vez que se use un numero real como indice de una matriz Sin embargo la especificacion del lenguaje puede imponer restricciones a los operandos por ejemplo cuando un operador aritmetico binario se aplica a un numero entero y a un numero real 3 Revisa que los arreglos tengan definido el tamano correcto Fase de sintesis Editar Consiste en generar el codigo objeto equivalente al programa fuente Solo se genera codigo objeto cuando el programa fuente esta libre de errores de analisis lo cual no quiere decir que el programa se ejecute correctamente ya que un programa puede tener errores de concepto o expresiones mal calculadas Por lo general el codigo objeto es codigo de maquina relocalizable o codigo ensamblador Las posiciones de memoria se seleccionan para cada una de las variables usadas por el programa Despues cada una de las instrucciones intermedias se traduce a una secuencia de instrucciones de maquina que ejecuta la misma tarea Un aspecto decisivo es la asignacion de variables a registros Generacion de codigo intermedio Editar Despues de los analisis sintactico y semantico algunos compiladores generan una representacion intermedia explicita del programa fuente Se puede considerar esta representacion intermedia como un programa para una maquina abstracta Esta representacion intermedia debe tener dos propiedades importantes debe ser facil de producir y facil de traducir al programa objeto La representacion intermedia puede tener diversas formas Existe una forma intermedia llamada codigo de tres direcciones que es como el lenguaje ensamblador de una maquina en la que cada posicion de memoria puede actuar como un registro El codigo de tres direcciones consiste en una secuencia de instrucciones cada una de las cuales tiene como maximo tres operandos Esta representacion intermedia tiene varias propiedades Primera cada instruccion de tres direcciones tiene a lo sumo un operador ademas de la asignacion por tanto cuando se generan estas instrucciones el traductor tiene que decidir el orden en que deben efectuarse las operaciones Segunda el traductor debe generar un nombre temporal para guardar los valores calculados por cada instruccion Tercera algunas instrucciones de tres direcciones tienen menos de tres operandos por ejemplo la asignacion Optimizacion de codigo Editar Articulo principal Compilador optimizador La fase de optimizacion de codigo consiste en mejorar el codigo intermedio de modo que resulte un codigo maquina mas rapido de ejecutar Esta fase de la etapa de sintesis es posible sobre todo si el traductor es un compilador dificilmente un interprete puede optimizar el codigo objeto Hay mucha variacion en la cantidad de optimizacion de codigo que ejecutan los distintos compiladores En los que hacen mucha optimizacion llamados compiladores optimizadores una parte significativa del tiempo del compilador se ocupa en esta fase Sin embargo hay optimizaciones sencillas que mejoran sensiblemente el tiempo de ejecucion del programa objeto sin retardar demasiado la compilacion 3 Estructura de datos principales EditarLa interaccion entre los algoritmos utilizados por las fases del compilador y las estructuras de datos que soportan estas fases es naturalmente muy fuerte El escritor del compilador se esfuerza por implementar estos algoritmos de una manera tan eficaz como sea posible sin aumentar demasiado la complejidad De manera ideal un compilador deberia poder compilar un programa en un tiempo proporcional al tamano del mismo Componentes lexicos o tokenes Editar Cuando un analizador lexico reune los caracteres en un token generalmente representa el token de manera simbolica es decir como un valor de un tipo de datos enumerado que representa el conjunto de tokens del lenguaje fuente En ocasiones tambien es necesario mantener la cadena de caracteres misma u otra informacion derivada de ella tal como el nombre asociado con un token identificador o el valor de un token de numero En la mayoria de los lenguajes el analizador lexico solo necesita generar un token a la vez En este caso se puede utilizar una variable global simple para mantener la informacion del token En otros casos cuyo ejemplo mas notable es FORTRAN puede ser necesario un arreglo o vector de tokenes Arbol sintactico Editar Si el analizador sintactico genera un arbol sintactico por lo regular se construye como una estructura estandar basada en un puntero que se asigna de manera dinamica a medida que se efectua el analisis sintactico El arbol entero puede entonces conservarse como una variable simple que apunta al nodo raiz Cada nodo en la estructura es un registro cuyos campos representan la informacion recolectada tanto por el analizador sintactico como posteriormente por el analizador semantico Por ejemplo el tipo de datos de una expresion puede conservarse como un campo en el nodo del arbol sintactico para la expresion En ocasiones para ahorrar espacio estos campos se asignan de manera dinamica o se almacenan en otras estructuras de datos tales como la tabla de simbolos que permiten una asignacion y desasignacion selectivas En realidad cada nodo del arbol sintactico por si mismo puede requerir de atributos diferentes para ser almacenado de acuerdo con la clase de estructura del lenguaje que represente En este caso cada nodo en el arbol sintactico puede estar representado por un registro variable con cada clase de nodo conteniendo solamente la informacion necesaria para ese caso Tabla de simbolos Editar Esta estructura de datos mantiene la informacion asociada con los identificadores funciones variables constantes y tipos de datos La tabla de simbolos interactua con casi todas las fases del compilador el analizador lexico el analizador sintactico o el analizador semantico pueden introducir identificadores dentro de la tabla el analizador semantico agregara tipos de datos y otra informacion y las fases de optimizacion y generacion de codigo utilizaran la informacion proporcionada por la tabla de simbolos para efectuar selecciones apropiadas de codigo objeto Puesto que la tabla de simbolos tendra solicitudes de acceso con tanta frecuencia las operaciones de insercion eliminacion y acceso necesitan ser eficientes preferiblemente operaciones de tiempo constante Una estructura de datos estandar para este proposito es la tabla de dispersion o de calculo de direccion aunque tambien se pueden utilizar diversas estructuras de arbol En ocasiones se utilizan varias tablas y se mantienen en una lista o pila Tabla de literales Editar La busqueda y la insercion rapida son esenciales tambien para la tabla de literales la cual almacena constantes y cadenas utilizadas en el programa Sin embargo una tabla de literales necesita impedir las eliminaciones porque sus datos se aplican globalmente al programa y una constante o cadena aparecera solo una vez en esta tabla La tabla de literales es importante en la reduccion del tamano de un programa en la memoria al permitir la reutilizacion de constantes y cadenas Tambien es necesaria para que el generador de codigo construya direcciones simbolicas para las literales y para introducir definiciones de datos en el archivo de codigo objeto Codigo intermedio Editar De acuerdo con la clase de codigo intermedio por ejemplo codigo de tres direcciones o codigo P y de las clases de optimizaciones realizadas este codigo puede conservarse como un arreglo de cadenas de texto un archivo de texto temporal o bien una lista de estructuras ligadas En los compiladores que realizan optimizaciones complejas debe ponerse particular atencion a la seleccion de representaciones que permitan una facil reorganizacion Generacion de codigo intermedioDespues de los analisis sintactico y semantico algunos compiladores generan una representacion intermedia explicita del programa fuente Se puede considerar esta representacion intermedia como un programa para una maquina abstracta Esta representacion intermedia debe tener dos propiedades importantes debe ser facil de producir y facil de traducir al programa objeto La representacion intermedia puede tener diversas formas Existe una forma intermedia llamada codigo de tres direcciones que es como el lenguaje ensamblador para una maquina en la que cada posicion de memoria puede actuar como un registro El codigo de tres direcciones consiste en una secuencia de instrucciones cada una de las cuales tiene como maximo tres operandos El programa fuente de 1 puede aparecer en codigo de tres direcciones como temp1 entreal 60 temp2 id3 temp1 gt 2 temp3 id2 temp2 id1 temp3 Esta representacion intermedia tiene varias propiedades Primera cada instruccion de tres direcciones tiene a lo sumo un operador ademas de la asignacion Por tanto cuando se generan esas instrucciones el compilador tiene que decidir el orden en que deben efectuarse las operaciones la multiplicacion precede a la adicion al programa fuente de Segunda el compilador debe generar un nombre temporal para guardar los valores calculados por cada instruccion Tercera algunas instrucciones de tres direcciones tienen menos de tres operadores por ejemplo la primera y la ultima instrucciones de asignacion Optimizacion de codigoLa fase de optimizacion de codigo trata de mejorar el codigo intermedio de modo que resulte un codigo de maquina mas rapido de ejecutar Algunas optimizaciones son triviales Por ejemplo un algoritmo natural genera el codigo intermedio 2 utilizando una instruccion para cada operador de la representacion del arbol despues del analisis semantico aunque hay una forma mejor de realizar los mismos calculos usando las dos instrucciones temp1 id3 60 0 gt 3 id1 id2 temp1 Este sencillo algoritmo no tiene nada de malo puesto que el problema se puede solucionar en la fase de optimizacion de codigo Esto es el compilador puede deducir que la conversion de 60 de entero a real se puede hacer de una vez por todas en el momento de la compilacion de modo que la operacion entreal se puede eliminar Ademas temp3 se usa solo una vez para transmitir su valor a id1 Entonces resulta seguro sustituir a id1 por temp3 a partir de lo cual la ultima proposicion de 2 no se necesita y se obtiene el codigo de 3 Hay muchas variaciones en la cantidad de optimizacion de codigo que ejecutan los distintos compiladores En lo que hacen mucha optimizacion llamados compiladores optimizadores una parte significativa del tiempo del compilador se ocupa en esta fase Sin embargo hay optimizaciones sencillas que mejoran sensiblemente el tiempo de ejecucion del programa objeto sin retardar demasiado la compilacion Archivos temporales Editar Al principio las computadoras no tenian la suficiente memoria para guardar un programa completo durante la compilacion Este problema se resolvio mediante el uso de archivos temporales para mantener los productos de los pasos intermedios durante la traduccion o bien al compilar al vuelo es decir manteniendo solo la informacion suficiente de las partes anteriores del programa fuente que permita proceder a la traduccion Las limitaciones de memoria son ahora un problema mucho menor y es posible requerir que una unidad de compilacion entera se mantenga en memoria en especial si se dispone de la compilacion por separado en el lenguaje Con todo los compiladores ocasionalmente encuentran util generar archivos intermedios durante alguna de las etapas del procesamiento Algo tipico de estos es la necesidad de direcciones de correccion hacia atras durante la generacion de codigo Vease tambien EditarBlueJ Lenguaje de programacion Proceso de traduccion de programas Lenguaje ensamblador Ensamblador Desensamblador Decompilador Interprete Depurador Lenguaje de alto nivel Lenguaje de bajo nivel Lenguaje de maquina Historia de la construccion de los compiladoresLibros Principles of Compiler Design Compilers Principles Techniques and ToolsReferencias Editar Clocksin William 1997 Clause and effect Springer Verlag p 93 ISBN 978 3 540 62971 9 Laborda Javier Josep Galimany Rosa Maria Pena Antoni Gual 1985 Software Biblioteca practica de la computacion Barcelona Ediciones Oceano Exito S A La referencia utiliza el parametro obsoleto coautores ayuda a b Aho Alfred V Ravi Sethi Jeffrey D Ullman 2008 Introduccion a la Compilacion Compiladores Principios tecnicas y practicas Mexico Addison Wesley La referencia utiliza el parametro obsoleto coautores ayuda Enlaces externos Editar Wikcionario tiene definiciones y otra informacion sobre compilador Let s Build a Compiler Tutorial de Jack W Crenshaw sobre como hacer un compilador Java a tope Traductores y Compiladores con Lex Yacc JFlex Cup y JavaCC Libro basico sobre compiladores Datos Q47506 Multimedia CompilersObtenido de https es wikipedia org w index php title Compilador amp oldid 133310275, wikipedia, wiki, leyendo, leer, libro, biblioteca,

español

, española, descargar, gratis, descargar gratis, mp3, video, mp4, 3gp, jpg, jpeg, gif, png, imagen, música, canción, película, libro, juego, juegos