Un compilador es un programa informático que traduce un
programa escrito en un lenguaje de programación a otro lenguaje de
programación, generando un programa equivalente que la máquina será capaz de
interpretar. Usualmente el segundo lenguaje es lenguaje de máquina, pero
también puede ser un código intermedio (bytecode), o simplemente texto. Este
proceso de traducción se conoce como compilación.
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.exe para Microsoft 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 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
sólo 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.
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
(tokens), 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:
Cualquier identificador es una expresión.
Cualquier número es una expresión.
Si expresión1 y expresión2 son expresiones, entonces también
lo son:
expresión1 + expresión2
expresión1 * expresión2
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.2 Revisa que los
arreglos tengan definido el tamaño correcto.
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.
0 comentarios:
Publicar un comentario