miércoles, 30 de mayo de 2012

3. EL PROCESO DE COMPILACIÓN


Por ser los compiladores el tipo de traductor más utilizado en la actualidad, desarrollaremos el proceso de compilación, que consiste en la traducción de un programa fuente, escrito en lenguaje de alto nivel, a su correspondiente programa objeto, escrito en lenguaje máquina, dejándolo listo para la ejecución con poca o ninguna preparación adicional.
Antes del proceso de compilación, se crea el programa fuente utilizando cualquier aplicación disponible con capacidades de edición de textos.
            Para llevar a cabo la compilación, el programa debe residir en memoria principal simultáneamente con el compilador. El resultado de la compilación puede dar lugar a la aparición de errores, en cuyo caso no se genera el programa objeto, sino que se realiza un listado de compilación, un informe indicando la naturaleza y situación de los errores detectados por el compilador en el programa fuente. Con el programa editor se corrigen y se comienza de nuevo el proceso.
            Una vez finalizada la compilación y obtenido el programa objeto, se somete a un proceso de montaje donde se enlazan los distintos módulos que lo componen, en caso de tratar con programas que poseen subprogramas (que pueden ser compilados separadamente). Además, se incorporan las denominadas rutinas de biblioteca en caso de solicitarlas el propio programa. Este proceso de montaje es realizado por un programa denominado montador o encuadernador, que también recibe el nombre de editor de enlace o linker.
La compilación es un proceso complejo que consume a veces un tiempo muy superior a la propia ejecución del programa. Este proceso consta, en general, de dos etapas fundamentales: la etapa de análisis del programa fuente y la etapa de síntesis del programa objeto. Cada una de estas etapas conlleva la realización de varias fases.
            El compilador utiliza internamente una tabla de símbolos para introducir determinados datos que necesita. Esta tabla interviene prácticamente en todas las fases del proceso de compilación; así mismo, el compilador posee un módulo de tratamiento de errores que permite determinarlas reacciones que se deben producir ante la aparición de cualquier tipo de error.

            3.1. Análisis del programa fuente

            Durante esta etapa, el programa fuente se divide en sus elementos componentes, creándose una representación intermedia del mismo. Durante este proceso, es posible detectar posibles errores en la escritura del programa.

            3.1.1. Análisis lexicográfico

            Se examina el programa fuente de izquierda a derecha, reconociendo las unidades básicas de información pertenecientes al lenguaje. Estas unidades básicas se denominan unidades léxicas o tokens. Un token es un elemento o cadena (secuencia de caracteres) que tiene un significado propio en el lenguaje.
Además de reconocer cada token, almacena en la tabla de símbolos aquella información del símbolo que pueda ser necesaria para las restantes fases de compilación.

            En el caso de que no existan errores en este primer análisis, se obtiene una representación del programa formada por:

            • La descripción de símbolos en la tabla de símbolos.

            • Una secuencia de símbolos en la que se incluye, junto a cada símbolo, una referencia a la ubicación de dicho símbolo en la tabla de símbolos. Esta secuencia es denominada tira de tokens.
En esta nueva representación, se ha eliminado previamente toda la información superflua, como comentarios, tabulaciones y espacios en blanco no significativos.
Los errores que pueden ser reconocidos se deben a la detección de cadenas de caracteres del programa fuente que no se ajustan al patrón de ningún token, como identificadores de variables incorrectos, números expresados incorrectamente, cadenas de caracteres mal delimitadas, etc.

            3.1.2. Análisis sintáctico

            La sintaxis de un lenguaje de programación especifica cómo deben escribirse los programas basándose en un conjunto de reglas sintácticas que determinan la gramática del lenguaje. Un programa es sintácticamente correcto cuando sus estructuras (expresiones, sentencias, asignaciones, etc.) aparecen dispuestas de forma correcta de acuerdo a la gramática del lenguaje.
El análisis sintáctico recibe la tira de tokens del analizador lexicográfico e investiga en ella los posibles errores sintácticos que aparezcan, como expresiones estructuradas incorrectamente, falta de algún token dentro de una sentencia, formato incorrecto en una asignación, etc.
En caso de no encontrar errores, se agrupan los componentes léxicos en frases gramaticales que serán utilizadas en la síntesis del programa objeto. Estas frases gramaticales se suelen representar mediante un árbol sintáctico que pone de manifiesto la estructura jerárquica de los componentes de un programa.

            3.1.3. Análisis semántico
                       
            Una construcción de un lenguaje de programación puede ser sintácticamente correcta y carecer de significado. Por tanto, si queremos generar código en lenguaje máquina con el mismo significado que el código fuente, tendremos que determinar la validez semántica de las construcciones del código fuente.
El análisis semántico se encarga de estudiar la coherencia semántica del código fuente a partir de la identificación de las construcciones sintácticas y de la información almacenada en la tabla de símbolos. El análisis semántico debe ser capaz de detectar construcciones sin un significado correcto. Por ejemplo, asignar a una variable de tipo entero un valor de tipo cadena de caracteres, sumar dos valores booleanos, etc.

            3.2. Síntesis del programa objeto

            Construye el programa objeto a partir de la representación obtenida en la etapa de análisis.

            3.2.1. Generación de código intermedio

            Se traduce el resultado de la etapa de análisis (en el caso de ausencia de errores) a un lenguaje intermedio propio del compilador. Esta representación intermedia (código intermedio) debe poseer las siguientes características:

            • Debe ser independiente de la máquina.

            • Debe ser fácil de producir.
           
            • Debe ser fácil de traducir a un lenguaje máquina.
            Esta fase aparece, generalmente, en aquellos compiladores construidos para su implantación en distintas máquinas, ya que su existencia hace que todos los módulos de análisis del compilador sean comunes para todas las máquinas (aumenta la portabilidad del compilador y se abaratan sus costes de construcción).

            3.2.2. Optimización de código
           
            Esta fase se suele introducir para mejorar la eficiencia del código producido por el compilador. Se toma el código intermedio de forma global y se optimiza, adaptándolo a las características del procesador al que va dirigido para mejorar los resultados de su ejecución.
La optimización se realiza en cualquier parte del programa, pero es en los bucles donde se consiguen los mejores resultados; por ejemplo, sacar una sentencia que se repite fuera del bucle, consiguiendo que sólo se ejecute una vez. De esta forma, se consigue un programa igualmente correcto, pero que consume menos tiempo de ejecución.
Un problema de la optimización es que incrementa el tamaño y la complejidad del compilador.

            3.2.3. Generación de código objeto

            Se traduce el código intermedio optimizado a código final, es decir, al lenguaje máquina (en algunos casos a ensamblador) del procesador al que va dirigido el compilador. Para ello, se reserva la memoria necesaria para cada elemento del código intermedio, y cada instrucción en lenguaje intermedio es traducida a varias instrucciones en lenguaje máquina.

            3.3. Detección y tratamiento de errores

            Aunque es en la etapa de análisis donde se detectan la mayoría de los errores, cada etapa del proceso puede encontrar errores. Para tratar adecuadamente un error, interviene el módulo de tratamiento de errores que comprende generalmente la realización de dos acciones:

            • Diagnóstico del error: Trata de buscar su localización exacta y la posible causa del mismo, para ofrecer al programador un mensaje de diagnóstico, que será incluido en el listado de compilación.

            • Recuperación del error: Después de detectado un error, cada fase debe tratarlo de alguna forma para poder continuar con la compilación y permitir la detección de más errores, aunque no se genere código objeto.
Los tipos de errores que puede tener un programa son los siguientes:

            • Errores lexicográficos: Se producen por la aparición de tokens no reconocibles, es decir, cadenas que no se ajustan al patrón de ningún símbolo elemental del lenguaje.

            • Errores sintácticos: Aparecen cuando se violan las reglas de sintaxis del lenguaje.

            • Errores semánticos: Se detectan cuando aparece una secuencia sintácticamente correcta, pero que carece de sentido dentro del contexto del programa fuente.

            • Errores lógicos: Son debidos a la utilización de un algoritmo o expresión incorrecta para el problema que se trata de resolver.

            • Errores de ejecución: Son errores relacionados con desbordamientos, operaciones matemáticamente irresolubles, etc.
En ocasiones, se indican determinados errores que pueden existir pero que no perjudican al resto del proceso de compilación e incluso pueden permitir el funcionamiento del programa final. Estos mensajes de error se denominan advertencias o warnings. Un ejemplo típico puede ser la
declaración de una variable que no se utiliza en ningún bloque del programa.

No hay comentarios:

Publicar un comentario