Beruflich Dokumente
Kultur Dokumente
Asignación Estática
Los nombres se enlazan a las posiciones de memoria durante la compilación del
programa.
No requiere de un programa run-time
Cada procedimiento tiene asignado un registro de activación, cuya posición de memoria
es asignada en la compilación
Los valores de las variables locales permanecen entre diferentes activaciones del mismo
procedimiento
Esto implica que:
El tamaño de los objetos de datos debe ser conocido en tiempo de compilación.
o No es posible declarar procedimientos recursivos.
o No pueden usarse estructuras de datos dinámicas.
Ejemplo: Fortran
Implementa la pila de control: el almacenamiento es manejado como una pila, y los
registros de activación entran a la pila cuando inicia la ejecución del procedimiento, y
salen de ella cuando termina.
Los valores de las variables locales se pierden cuando la activación termina.
Un registro marca el tope de la pila, cuando se activa el procedimiento, se asigna el
espacio para su registro de activación.
Los lenguajes que utilizan asignación estática de memoria como Fortran necesitan conocer el
tipo y el tamaño de memoria de todos los objetos en compilación: Es decir, hay que declarar
los nombres antes de utilizarlos.
CALL RECEPTOR
RETURN
HALT
ACTION
Por ejemplo, el código de tres direcciones para los procedimientos c y p de la figura 9.4 contiene
precisamente estas clases de proposiciones. El tamaño y la disposición de los registros de
activación se comunican al generador de código a través de la información de la tabla de
símbolos sobre los nombres. Para verlo con claridad, en la figura 9,4 se muestra la disposición
en lugar de la forma de las entradas de la tabla de símbolos.
Primero vamos a considerar el código necesario para implementar el caso más simple, la
asignación estática. Aquí, una instrucción call receptor en el código intermedio puede
implementarse mediante una secuencia de dos instrucciones de máquina destino:
El código para el primer procedimiento inicializa la pila, estableciendo SP al inicio del área de la
pila en la memoria:
Este método tiene una ven taja considerable; hace que el compilador sea más portable, ya que
el front-end no tiene que modificarse, ni siquiera cuan do el compilador se mueve a una
Máquina distinta, e n donde se requiere una organización distinta en tiempo de ejecución. Por
otro lado, la acción de generar la secuencia específica de pasos de acceso mientras se gen era el
código intermedio puede representar un a ventaja considerable en un compilador optimizador.
SALIDA: Una lista de los bloques básicos para la secuencia en la que cada instrucción se asigna
exactamente a un bloque básico.
MÉTODO: En primer lugar, determinamos las instrucciones en el código intermedio que son
líderes; es decir, las primeras instrucciones en algún bloque básico. La instrucción que va justo
después del programa intermedio no se incluye como líder. L as reglas para buscar líderes son:
3. Cualquier instrucción que siga justo después de un salto condicional o incondicional es líder.
Es esencial saber cuándo se usará el valor de una variable para generar buen código. Si el valor
de una variable que s e encuentra en un registro nunca se utilizará más adelante, entonces ese
registro puede asignarse a otra variable.
Deseamos determinar para la instrucción de tres direcciones x — y + z cuáles van a ser los
siguientes usos de( x, y) y 2. Por e l momento, no nos preocuparemos por los usos fuera del
bloque básico que contiene esta instrucción de tres direcciones.
Nuestro algoritmo para determinar la información sobre la vida y el siguiente uso realiza una
pasada invertida sobre cada bloque básico.
ENTRADA: Un bloque básico B de instrucciones de tres direcciones. Suponemos que, al
principio, la tabla de símbolos muestra que todas las variables no temporales en B están vivas
al salir.
A menudo agregamos dos nodos, conocidos como entrada y salida, los cuales no corresponden
a instrucciones intermedias ejecutables. H ay u n a flecha que va desde la entrada hasta el primer
nodo ejecutable del grafo de flujo; es decir, al bloque básico que surge de la primera instrucción
del código intermedio. Hay una flecha que va a la salid a desde cualquier bloque básico que con
tenga una instrucción, que pudiera ser la última instrucción ejecutada del programa.
Si la instrucción final del programa no es un salto incondicional, entonces el bloque que contiene
la instrucción final del programa es un predecesor de la salida, p ero también lo es cualquier b
lo que básico que tenga un salto hacia código que no forme parte del programa.
En primer lugar, observe en la figura 8.9 que en el grafo de flujo, es normal sustituir los saltos
hacia números de instrucción o etiquetas, por saltos hacia bloques básicos. Recuerde q u e
todo salto condicional o incondicional va hacia la instrucción líder de algún bloque básico, y
que el salto ahora hará referencia a este bloque. La razón de este cambio es que después de
construir el grafo de flujo, es común realizar cambios considerables a las instrucciones e n los
diversos bloques básicos. Si los saltos fueran a instrucciones, tendríamos que corregir los
destinos de éstos cada vez que se modificara una de las instrucciones de destino.
Siendo grafos bastante ordinarios, los grafos de flujo pueden representarse mediante una de
las estructuras de datos apropiadas para grafos. El contenido de los nodos (bloques básicos)
necesita su propia representación. Podríamos representar el contenido de un nodo mediante
un apuntador a la instrucción líder en e l arreglo de instrucciones de tres direcciones, junto
con un conteo del número de instrucciones, o un segundo apuntador a la última instrucción.
Sin embargo, como tal vez cambiemos el número de instrucciones en un bloque básico con
frecuencia, es probable que sea más eficiente crear una lista enlazada de instrucciones para
cada bloque básico.