Documentos de Académico
Documentos de Profesional
Documentos de Cultura
1
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.
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.
2
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.
Á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.
3
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 des asignación selectiva. 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.
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
4
realizan optimizaciones complejas debe ponerse particular atención a la
selección de representaciones que permitan una fácil reorganizació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
5
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).
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.