You are on page 1of 14

4o Ingenier´ ıa Inform´ atica

II26 Procesadores de lenguaje
Organizaci´ on y gesti´ on de la memoria

Esquema del tema
1. Introducci´ on 2. Organizaci´ on de la memoria en tiempo de ejecuci´ on 3. Memoria est´ atica 4. Memoria de pila 5. Memoria con reserva din´ amica 6. Otros aspectos de las subrutinas 7. Resumen

1.

Introducci´ on

Una vez terminadas las fases de an´ alisis, comienza la generaci´ on de c´ odigo. Como paso previo a la generaci´ on, en este tema veremos c´ omo se organiza la memoria de los programas. Nos centraremos en dos aspectos fundamentales: la gesti´ on de las variables globales y de la memoria asociada a las subrutinas. Adem´ as, comentaremos algunos aspectos de la gesti´ on de la memoria din´ amica.

2.

Organizaci´ on de la memoria en tiempo de ejecuci´ on

Podemos distinguir tres tipos de memoria. En primer lugar, est´ a la ocupada por el c´ odigo del programa. Por otro lado, est´ a la destinada a datos est´ aticos, que se gestiona en tiempo de compilaci´ on. Finalmente, tenemos la memoria que debe gestionarse en tiempo de ejecuci´ on. Hay poco que decir sobre la memoria ocupada por el programa. Su tama˜ no es conocido en tiempo de compilaci´ on y, en la mayor´ ıa de los lenguajes modernos, se puede decir que es “invisible”: el c´ odigo no puede referirse a s´ ı mismo ni modificarse. La memoria destinada a datos est´ aticos se utiliza para las variables globales y algunas otras como las variables est´ aticas de las subrutinas. La gesti´ on de esta memoria es sencilla y se puede realizar en tiempo de compilaci´ on. Al hablar de la memoria gestionada en tiempo de ejecuci´ on, podemos distinguir entre la memoria que se utiliza para albergar los objetos que se crean y destruyen con la ejecuci´ on de las subrutinas (par´ ametros, variables locales y algunos datos generados por el compilador) y la que se suele conocer como memoria din´ amica, que se reserva expl´ ıcitamente por el programador o que se necesita para almacenar objetos con tiempos de vida o tama˜ nos desconocidos en tiempo de compilaci´ on. La memoria asociada a las subrutinas ser´ a gestionada mediante una pila. La memoria din´ amica se localizar´ a en un heap.

Basta con que el compilador tenga anotada la primera direcci´ on libre del bloque de memoria est´ atica y la vaya actualizando sumando la talla de los objetos que va reservando.. Memoria est´ atica La gesti´ on de la memoria est´ atica es la m´ as sencilla. desperdiciar´ a memoria.. la memoria se organiza de una manera similar a: C´ odigo Datos est´ aticos Heap .. en los restantes ejemplos supondremos que la pila crece hacia arriba2 ya que esta es la forma m´ as c´ omoda de implementarla en la m´ aquina virtual que utilizamos en pr´ acticas.. Por estas razones.. ↓ ↑ ... De hecho. Normalmente. si son peque˜ nas.. el compilador deber´ a generar c´ odigo para que en caso de colisi´ on el programa pueda pedir memoria adicional al sistema o terminar adecuadamente. Sin embargo.. no podr´ a utilizar el programa en todos los casos.. 3.. Obviamente. el programa u ´nicamente necesita saber en qu´ e direcci´ on se encuentra. Las acciones que tiene 1 Tambi´ en es habitual que “hacia abajo” signifique “hacia arriba” en el dibujo: la direcci´ on 0 ocupa la parte superior... existen bastantes inconvenientes: El tama˜ no de los objetos debe ser conocido en tiempo de compilaci´ on: no se puede trabajar con objetos de longitud variable... Supongamos que A es la primera direcci´ on libre para datos globales. Sin embargo.. Estas direcciones se pueden asignar secuencialmente.... Pila Es habitual que la pila crezca “hacia abajo”1 debido a que muchos procesadores tienen instrucciones que facilitan la creaci´ on de pilas “descendentes”. La gesti´ on de esta memoria se puede hacer ´ ıntegramente en tiempo de compilaci´ on. Para acceder a un objeto.. Las principales ventajas son la sencillez de implementaci´ on y que los requerimientos de memoria del programa son conocidos una vez compilado el mismo.. .. u ´nicamente tienen reserva est´ atica de memoria..... el sistema operativo ofrece ayuda tanto para la detecci´ on de la colisi´ on (suele hacerlo con ayuda del hardware) como para facilitar el aumento de tama˜ no del bloque. 2 Y haremos los dibujos con arriba en la parte superior :-).. como las primeras versiones de FORTRAN.. Es dif´ ıcil para el programador definir el tama˜ no de las estructuras que va a usar: si son demasiado grandes.... Dado que la pila puede colisionar con el heap.. la organizaci´ on depender´ a en u ´ltima instancia del sistema operativo que es el que determina el modelo de proceso que se utiliza.2 II26 Procesadores de lenguaje Normalmente.... casi todos los lenguajes de programaci´ on tienen adem´ as la posibilidad de gestionar parte de la memoria de manera din´ amica. algunos lenguajes de programaci´ on antiguos. S´ olo puede haber una instancia de cada objeto: no se pueden implementar procedimientos recursivos..

lo habitual es comenzar por la direcci´ on cero. en un momento dado de la ejecuci´ on del programa. 4. fin para toda Fin Asignaci´ on est´ atica Respecto a este algoritmo. Esta forma de actuar sugiere que podemos representar las activaciones de las subrutinas mediante una pila. Es m´ as. Si el espacio de direcciones se dispone como hemos comentado al comienzo del tema. En cuanto al momento de ejecutar el algoritmo. al finalizar la ejecuci´ on. procedimiento. bastando con los objetos que se creen para albergar la informaci´ on acerca de los identificadores. } main() { fib(2). y eso implica la ejecuci´ on de su cuerpo. hay que aclarar una serie de cosas. el valor de A depender´ a de la arquitectura de la m´ aquina que vayamos a utilizar. la llamamos funci´ on y. no hace falta que lo hagamos en una fase separada. para toda declaraci´ on global d hacer d. Cuando esta termine. tendremos una serie de subrutinas activas de modo que cada una ha llamado a la siguiente y la u ´ltima es la que est´ a en ejecuci´ on.1. Si el nombre de una subrutina (con los par´ ametros que necesite) aparece en una sentencia ejecutable. Una definici´ on de subrutina es una declaraci´ on que asocia un identificador (nombre de la subrutina) a un perfil (tipo de los par´ ametros y valor de retorno) y una serie de sentencias (cuerpo de la subrutina). dlibre := dlibre + d. Por otro lado. Activaci´ on y tiempo de vida de las subrutinas Es habitual suponer que las subrutinas se ejecutan de modo que su ejecuci´ on comienza por el inicio de su cuerpo y que. devolver´ a el control a la anterior y as´ ı sucesivamente. si la m´ aquina permite que los datos est´ en en su propio segmento o se va a utilizar un enlazador que los pueda mover. Si la subrutina devuelve un valor. el control regresa al punto desde el que la subrutina fue llamada. } c Universitat Jaume I 2006-2007 . 4. Por ejemplo. decimos que se llama a la subrutina. En primer lugar.dir := dlibre . si no. Memoria de pila En esta secci´ on supondremos que un programa se compone de una serie de subrutinas. Denominamos activaci´ on de una subrutina a cada una de sus ejecuciones. Ciertos identificadores declarados en una subrutina son especiales: los par´ ametros formales. puede ser necesario generar las direcciones relativas a una etiqueta y dejar que el ensamblador o el enlazador las resuelva adecuadamente. sea el siguiente programa: int fib(int n) { if (n <= 1) return 1. else return fib(n-1)+fib(n-2).talla .Organizaci´ on y gesti´ on de la memoria 3 que realizar el compilador para asignar las direcciones son: Algoritmo Asignaci´ on est´ atica dlibre := A. Si tenemos dlibre como una variable global (o accesible en alg´ un m´ odulo del compilador) podemos realizar la actualizaci´ on al crear el nodo correspondiente a la declaraci´ on. Esto implica que. Dado que la longitud de ´ este no se conoce hasta el fin de la compilaci´ on. entre las cuales hay una “principal”. Quien llama es el llamador (caller ) y el otro es el llamado (call´ ee ). el valor inicial deber´ a ser la direcci´ on inmediatamente posterior al c´ odigo. puede suceder que de esta manera no necesitemos crear nodos en el AST para las declaraciones. que recibir´ an valores concretos durante la ejecuci´ on mediante los par´ ametros de hecho (actual parameters ) o argumentos.

El compilador debe generar c´ odigo que gestione la pila. es decir.) Primero vamos a preocuparnos del caso en que no hay par´ ametros ni variables locales. el sistema operativo. y se desapila (se destruye) cuando finaliza la activaci´ on. el registro de activaci´ on contiene s´ olo una direcci´ on de retorno. valores de registros. reservar espacio de pila es sumar una cantidad positiva a la cima de la pila y liberar espacio de pila es restar una cantidad positiva. etc. la necesidad de enlazar con bibliotecas o programas escritos en otros lenguajes. . etc. el c´ odigo generado gestionar´ a una pila que mantendr´ a la informaci´ on relativa a todas las subrutinas activas en un momento dado de la ejecuci´ on. guardar informaci´ on de estado (direcci´ on de retorno. 4. En el momento de la llamada. podemos obtener una representaci´ on similar a la siguiente: fib(1) fib(2) main() llamada a fib(1) fib(0) fib(2) main() llamada a fib(0) main() inicio fib(2) main() llamada a fib(2) fib(2) main() fin de fib(1) fib(2) main() fin de fib(0) main() fin de fib(2) La memoria asociada a las subrutinas reflejar´ a esta estructura. Llamaremos registros o tramas de activaci´ on a estas celdas. Empezaremos por preocuparnos u ´nicamente de c´ omo “saltar” a una subrutina y c´ omo retornar al punto desde el que la hab´ ıamos llamado una vez finaliza su activaci´ on.) que permita reanudar la ejecuci´ on cuando finalice la activaci´ on. reservar espacio para las variables locales. Si la pila va de las direcciones bajas de memoria a las altas. si es pertinente). la direcci´ on a la que debe regresar el flujo de control cuando finalice la activaci´ on de la subrutina. Usaremos un registro3 .2. La pila empieza en una direcci´ on fija (t´ ıpicamente donde finaliza la zona de memoria para variables est´ aticas) que denominaremos BASE PILA. variables locales. En esta situaci´ on. el SP (por stack pointer ). para apuntar en tiempo de ejecuci´ on a la cima de la pila. El c´ odigo correspondiente a la activaci´ on (llamada) de la subrutina ser´ ıa: 3 Si no tenemos registros. Cada registro se apila (se crea) cuando se activa una subrutina. (Casi todos los ordenadores tienen instrucciones que facilitan la gesti´ on eficiente de la pila. Organizaci´ on de la pila La pila est´ a dividida en una serie de celdas que contienen informaci´ on relativa a la activaci´ on: direcci´ on de retorno. cuyo tiempo de vida est´ a incluido en el tiempo de vida de la activaci´ on. se debe: reservar espacio para los par´ ametros (y valor de retorno. argumentos. Complicaremos progresivamente los registros de activaci´ on a˜ nadi´ endoles informaci´ on. La forma exacta de cada los registros de activaci´ on depender´ a de muchos factores: la presencia de instrucciones especializadas en el procesador. utilizaremos una posici´ on fija de memoria. . Con PC denotamos el contador de programa. Para eso.4 II26 Procesadores de lenguaje Concentr´ andonos en la llamada a fib(2). Direcciones altas ³ SP Direcci´ on Direcci´ on Direcci´ on Direcci´ on de de de de retorno retorno retorno retorno Direcciones bajas ³BASE PILA Utilizaremos un pseudo-c´ odigo f´ acilmente traducible a c´ odigo de m´ aquina para describir las acciones que debemos ejecutar cuando se activa o se desactiva una subrutina. .

1 RA:= 0[SP] SALTO RA # JR RA Por ejemplo. secuencia de retorno. es decir. a=2. y JR. que guarda el valor del PC en el registro RA. en el llamado. cada llamada a una subrutina (y puede haber muchas) obliga a escribir una copia de la secuencia de llamada. la secuencia de retorno se escribe en la propia subrutina (llamado) y se denomina ep´ ılogo. podremos tener distintas divisiones de la activaci´ on entre el llamador y el llamado. es inevitable que parte de la secuencia de llamada est´ e en el llamador. Aun as´ ı.Organizaci´ on y gesti´ on de la memoria 5 0[SP] := PC + 3 SP := SP + 1 SALTO subrutina # Apilamos la direcci´ on a la que regresar # Saltamos a la subrutina Mientras que el de desactivaci´ on ser´ ıa: SP := SP . que salta a la direcci´ on almacenada en un registro.} El resultado de la compilaci´ on ser´ ıa: Inicio: SP := BASE_PILA JAL rut_main FINAL 0[SP] := RA SP := SP + 1 a := 1 SP := SP . En otro caso. Por ejemplo.1 SALTO 0[SP] # Restauramos la pila # Volvemos El c´ odigo de activaci´ on se denomina secuencia de llamada. la activaci´ on quedar´ ıa: Acciones del llamador (instrucci´ on JAL subrutina): RA := PC+2 SALTO subrutina Pr´ ologo (en el llamado ): 0[SP] := RA SP := SP + 1 Y el retorno: En el llamado (instrucci´ on JR RA): SP := SP . Dependiendo de las instrucciones disponibles en nuestra m´ aquina. void f(void) { a=1. pero interesa que la mayor parte est´ e en la propia subrutina (de la cual s´ olo hay una copia en el c´ odigo objeto).} void main(void) { f(). y el de desactivaci´ on.1 RA:= 0[SP] JR RA # Ep´ ılogo # Pr´ ologo # Pr´ ologo # Llamada a f # Ep´ ılogo # Ep´ ılogo # Ep´ ılogo rut_f: # Pr´ ologo # Pr´ ologo # Ep´ ılogo # Ep´ ılogo c Universitat Jaume I 2006-2007 . En principio. dado el programa C: int a.1 RA:= 0[SP] # Inicializaci´ on # Llamada a main JR RA rut_main: 0[SP] := RA SP := SP + 1 JAL rut_f a := 2 SP := SP . parece que la secuencia de llamada debe estar en el llamador. si tenemos las instrucciones JAL. La parte que escribimos en el llamado se denomina pr´ ologo. ¿D´ onde deben escribirse las secuencias de llamada y retorno? ¿En el llamador o en el llamado? Obviamente.

Los dos tipos m´ as comunes de paso de par´ ametros son: Paso por valor: es el m´ etodo m´ as sencillo. En el caso de las funciones. utilizamos un registro especial. muchos lenguajes permiten declarar variables con tiempo de vida limitado por el tiempo de activaci´ on de una subrutina: las variables locales.. Direcci´ on de retorno  el valor de retorno (en el caso de funciones). al menos:  vln  la direcci´ on de retorno de la subrutina.. Paso por referencia (o por direcci´ on. y a los par´ ametros con desplazamientos negativos sobre FP. Para poder acceder a las variables locales y a los par´ ametros de la subrutina. La idea es acceder uniformemente a variables locales y par´ ametros calculando su direcci´ on a partir del desplazamiento respecto de la direcci´ on apuntada por FP. Paso de par´ ametros Para se realmente u ´tiles. conviene ubicar la memoria que ocupan en el registro de activaci´ on. FP pasa a apuntar donde diga el enlace din´ amico. . . As´ ı. Par´ ametros  p1 Valor de retorno El valor de retorno puede no estar en la trama de activaci´ on. Funci´ on actual ³ FP Funciones anteriores El enlace din´ amico es el valor del FP al entrar en la subrutina y se utiliza para restaurarlo al salir de ella.. puntero de trama). en lo que sigue asumiremos que todo pasa a trav´ es de la trama de activaci´ on. Un posible convenio es hacer que se acceda a las variables locales con desplazamientos positivos sobre FP. que contiene el valor de FP que ten´ ıamos antes de activar la subrutina que ahora desactivamos.6 II26 Procesadores de lenguaje 4. pk  las variables locales. Una posible organizaci´ on es hacer que la trama de activaci´ on contenga. Por otra parte.3. Variables locales  vl1 los par´ ametros de hecho. Las modificaciones que se hagan a los par´ ametros formales en la subrutina no afectan a los par´ ametros de hecho. podemos considerar el valor de retorno un par´ ametro m´ as. Este orden se . Para simplificar. la tabla de s´ ımbolos trata de modo similar los par´ ametros y las variables locales: registra su posici´ on en memoria con un desplazamiento sobre el registro FP. Los par´ ametros de hecho se eval´ uan y los valores resultantes se pasan a la subrutina. Tambi´ en es posible pasar algunos par´ ametros mediante registros. o por lugar): Se pasa la direcci´ on de memoria donde se encuentra la informaci´ on que pasamos. La pila tiene un aspecto similar a: Espacio Libre                                    ³ SP Variables locales Enlace din´ amico Direcci´ on de retorno Par´ ametros de hecho Valor de retorno . Cuando desactivamos un registro. y no con una direcci´ on absoluta. las subrutinas deben poder recibir informaci´ on mediante el uso de par´ ametros... el FP (de Frame Pointer. Dado que el tiempo de vida de las variables locales est´ a incluido en el tiempo de activaci´ on.. Un convenio habitual es utilizar un registro para devolverlo si su tama˜ no es “peque˜ no” (cabe en un registro).

Cuando detectamos una funci´ on f anotamos en la tabla de s´ ımbolos: el tipo del valor de retorno y su tama˜ no.. de esta manera algunos registros son responsabilidad del llamado y otros del llamador. Un aspecto que no hemos tratado es qu´ e hacer con los registros que est´ en en uso en el momento de la llamada. etc.4. El convenio caller-saves estipula que el llamador guarda cualquier registro que le interese conservar. el llamador no necesita preocuparse de las variables locales de la subrutina para colocar los par´ ametros. Por ejemplo. se guardar´ a el resultado de f(a. } ser´ a de la forma d c Enlace din´ amico Direcci´ on de retorno a b Valor de retorno 4. Por ejemplo.). le basta con irlos apilando. L´ ogicamente.) y del tama˜ no total de la variables locales definidas en la funci´ on. con el que el llamado guarda aquellos registros que va a modificar. para cada par´ ametro y variable local anotamos una entrada con su nombre y la informaci´ on que necesitamos de ella (tipo. Generaci´ on de c´ odigo La tabla de s´ ımbolos debe registrar cierta informaci´ on sobre cada funci´ on definida en un programa y utilizarla cada vez que se llama a la funci´ on. Calcular el valor o direcci´ on de cada par´ ametro de hecho y apilarlo en el registro de activaci´ on. el tipo de cada par´ ametro formal. El c´ odigo de la funci´ on incluir´ a un pr´ ologo que debe realizar las siguientes acciones: Rellenar la informaci´ on del enlace din´ amico en el registro de activaci´ on. finalmente.d). supondremos que el llamador guarda en la pila los registros que quiera conservar. int b) { int c. . direcci´ on. si la pila crece “hacia abajo”. al generar c´ odigo para la expresi´ on f(a.d). c Universitat Jaume I 2006-2007 .Organizaci´ on y gesti´ on de la memoria 7 aplica si la pila crece“hacia arriba”. El c´ odigo correspondiente a una llamada a la funci´ on f hace lo siguiente: Guardar los registros activos. Recuperar los registros activos. Tambi´ en se pueden mezclar ambos convenios. Observa que con este orden.b) en un registro. Dado que este registro puede ser utilizado por la llamada f(c. del tama˜ no de otros campos del registro (la direcci´ on de retorno. habr´ a que asegurarse de que tenga el valor correcto a la hora de hacer la multiplicaci´ on. Otro convenio es el callee-saves. tanto para hacer comprobaci´ on de tipo (de argumentos y valor de retorno) como para generar c´ odigo que lleve a cabo la gesti´ on de la pila de activaciones. su tama˜ no y su “distancia” a FP. El llamado puede despu´ es preparar la pila a su gusto.b)*f(c. el registro de activaci´ on para int f(int a.. Aparte. Apilar la direcci´ on de retorno y saltar a la subrutina. el enlace din´ amico. el orden se invierte. anotamos tambi´ en el tama˜ no total del registro de activaci´ on que corresponder´ a a f. etc. d. Por simplicidad. Tenemos varias posibilidades. este valor es la suma de los tama˜ nos anteriores.

Esto es debido a que podemos aprovechar el ep´ ılogo para limpiar los par´ ametros de hecho..2)... Reflejo de los ´ ambitos en la tabla de s´ ımbolos Para poder almacenar la informaci´ on que hemos comentado en la secci´ on anterior... Al salir del bloque correspondiente.7 := 0[FP] := 1[FP] RA # # # # Restauramos SP Recuperamos RA Restauramos FP Volvemos Es interesante darse cuenta de la asimetr´ ıa entre lo que sumamos a SP en el pr´ ologo y lo que restamos en el ep´ ılogo. lo que. 4... El m´ as sencillo de solucionar es que el almacenamiento en la pila ya no se hace con un desplazamiento 0 sino que hay que ir cambi´ andolo en cada par´ ametro. El problema m´ as complejo es que puede suceder que tengamos llamadas anidadas (por ejemplo f(f(1. La segunda es la que se encargar´ a de eliminar las declaraciones contenidas en el bloque y que dejan de tener efecto.. La primera la utilizaremos cuando entremos en un bloque para se˜ nalar que las nuevas declaraciones estar´ an dentro de ´ el.5. si no se tiene mucho cuidado con el valor de SP. Es cierto. si el incremento se realiza en el llamado. puede hacer que el registro de activaci´ on de la llamada interna machaque el que estamos construyendo. Al encontrar una declaraci´ on. La entrada en el bloque consistir´ a en crear una lista que enlazar´ a las entradas que se creen en el bloque para facilitar su borrado a la salida. 0[SP] := a SP := SP + 1 . se eliminar´ an todas las declaraciones creadas dentro del bloque. . Esto sugiere a˜ nadir a las operaciones de la tabla dos m´ as: entrabloque y salbloque. . pero de hacerlo as´ ı se nos complica la generaci´ on de c´ odigo por dos aspectos.8 II26 Procesadores de lenguaje Actualizar SP (sum´ andole el tama˜ no total del registro de activaci´ on) y FP. La forma de funcionamiento de la tabla sugiere una organizaci´ on de la informaci´ on de los identificadores similar a una pila. 0[SP] := b SP := SP + 1 JAL rut_f . es necesario que la tabla de s´ ımbolos sea capaz de reflejar los ´ ambitos del programa. El c´ odigo generado para una llamada a la funci´ on f de la secci´ on anterior ser´ ıa: . la informaci´ on adecuada se almacenar´ a en el tope de la pila. Tendremos una tabla hash de manera que la informaci´ on de cada identificador ser´ a un puntero a una pila.3)). El pr´ ologo de f ser´ ıa: rut_f: 0[SP] 1[SP] FP := SP := := RA := FP SP SP + 4 # # # # Guardamos RA Guardamos FP Nuevo FP Incrementamos SP Y el ep´ ılogo: SP RA FP JR := SP . mejor a´ un. Tambi´ en puedes darte cuenta de que esta asimetr´ ıa es la que hace que usemos FP en lugar de SP para restaurar la direcci´ on de retorno y el propio FP. # Guardar registros # C´ alculo de a # C´ alculo de b # Llamada a f # Recuperar registros Puedes pensar que podemos ahorrar alguna instrucci´ on si agrupamos los incrementos del SP al final o. Lo habitual es que las reglas de ´ ambito sigan la estructura en bloques del programa: las declaraciones que se encuentran en un bloque son v´ alidas u ´nicamente en ese bloque y ocultan otras declaraciones para los mismos identificadores que est´ en fuera.

Los bloques pueden anidarse. . y cuando se elimina. VAR c: integer.j). Para reducir la ocupaci´ on en memoria. i<10. Bloques Otra manera de crear ´ ambitos es el empleo de variables locales a bloques. PROCEDURE d(j:real). for(j=0. la memoria se libera. 5. se puede asignar la misma direcci´ on a variables que est´ en en bloques distintos (y no anidados uno dentro del otro). Sin embargo.b: integer.j).i. La estructura de bloques puede implementarse mediante reserva de memoria de pila : cada bloque puede considerarse una subrutina sin par´ ametros que s´ olo se llama una vez. b c d i j 3 real 3 int 2 proc 2 int 2 real 1 1 int int 1 proc 3 2 1 4. j++) { printf("%d %d\n". c Universitat Jaume I 2006-2007 . j++) { printf("%d %d\n". } for(j=0.6.Organizaci´ on y gesti´ on de la memoria 9 Aqu´ ı podemos ver la situaci´ on de la tabla en un momento del an´ alisis de un programa: a PROGRAM ejemplo. se aplica la declaraci´ on del bloque m´ as pr´ oximo que contiene a B . i++) { int j. Memoria con reserva din´ amica La memoria que ocupan los objetos con tiempo de vida no predecible a partir del c´ odigo fuente se gestiona din´ amicamente: cuando se crea un objeto en tiempo de ejecuci´ on. Por ejemplo: main() { int i... se pide memoria para ´ el. Un bloque es una sentencia que contiene declaraciones locales propias. En algunos lenguajes encontramos caracter´ ısticas que necesitan una gesti´ on de memoria con reserva din´ amica: Los operadores new y dispose del PASCAL.i. es m´ as eficiente reservar memoria para todas las variables de la funci´ on simult´ aneamente. j<10. j<3. for(i=0. VAR a. VAR a: real. } } } /* Inicio bloque 0 */ /* Inicio bloque 1 */ /* Inicio bloque 2 */ /* Final bloque 2 */ /* Inicio bloque 3 */ /* Final bloque 3 */ /* Final bloque 1 */ /* Final bloque 0 */ Muchos lenguajes siguen la regla del bloque anidado m´ as pr´ oximo : el ´ ambito de una declaraci´ on hecha en un bloque alcanza a todo el bloque y si un nombre se referencia en un bloque B y no est´ a declarado en ´ el. PROCEDURE c(i:integer).

pero estudiarlas con detalle queda fuera de los objetivos del curso. . Caso general Es muy restrictivo exigir que los bloques sean siempre del mismo tama˜ no. Gesti´ on con bloques de tama˜ no fijo Empezamos por un supuesto sencillo: la memoria se solicita siempre en fragmentos de tama˜ no fijo. Vamos a estudiar muy por encima el problema de la gesti´ on del heap. Si hay fragmentaci´ on puede suceder que no se atiendan peticiones de memoria a´ un cuando haya suficiente memoria libre debido a que ´ esta est´ a repartida en bloques excesivamente peque˜ nos: petici´ on libre libre libre Para evitar una excesiva fragmentaci´ on cuando se libera memoria. Es habitual que la lista de bloques ocupados sea “virtual” si no se necesita hacer ning´ un recorrido sobre ella. la fragmentamos en dos partes. La zona de memoria destinada a atender las demandas de memoria din´ amica se denomina heap. hay que mirar si los bloques adyacentes est´ an libres y si es as´ ı.2. la creaci´ on de objetos en Java y su eliminaci´ on cuando ninguna variable mantiene referencias a dichos objetos.10 II26 Procesadores de lenguaje la creaci´ on de elementos de listas en LISP y su liberaci´ on autom´ atica (garbage collection ) por ausencia de referencias a la memoria. Cuando podemos pedir bloques de tama˜ nos diferentes. Ocupado 1 Libre 2 3 4 5 6 Al solicitar un bloque de memoria se nos da el primer bloque libre. que pasa a a˜ nadirse a la lista de bloques ocupados. una de b bytes y la otra de B − b bytes. los operadores new y delete o las llamadas impl´ ıcitas que comportan la creaci´ on y destrucci´ on de objetos en C++. Hay t´ ecnicas muy sofisticadas para reservar/liberar memoria eficientemente. Este m´ etodo es muy costoso: puede obligar a recorrer todo el heap con cada petici´ on de memoria. unirlos en un bloque libre u ´nico. El heap puede estructurarse en dos listas: una que encadena los bloques ocupados y otra que encadena los bloques libres. etc.1. las llamadas a funciones de bibiloteca malloc y free del C. 5. aparece el problema de la fragmentaci´ on. 5. Un m´ etodo para reservar bloques de memoria din´ amica es el de “primero donde quepa ” (first fit ): si se piden b bytes. y marcamos la primera como ocupada. Liberar un bloque lo elimina de la lista de bloques ocupados y lo a˜ nade a la lista de bloques libres. buscamos el primer trozo de memoria contigua con tama˜ no B superior a b. En la bibliograf´ ıa encontrar´ as m´ as informaci´ on.

el bloque se libera. esta es un ´ area de investigaci´ on muy activa actualmente y existen m´ etodos que hacen que la liberaci´ on autom´ atica de memoria sea una alternativa muy atractiva con un impacto muy reducido en el tiempo de ejecuci´ on. Cada vez que se hace una asignaci´ on a un puntero. el contador del primer bloque se anula y podemos liberarlo: p 0 q 3 r El problema de los contadores referencia es que no pueden liberar correctamente estructuras circulares. se decrementa el contador del bloque al que apuntaba y se incrementa el contador del nuevo bloque. aunque sean inaccesibles: p 1 1 c Universitat Jaume I 2006-2007 . Cuando el contador vale cero. 5. Contadores de referencia Cada bloque lleva un contador de los punteros que le apuntan. nos encontramos con que las cuentas de los bloques no se anulan. Python. LISP. Aunque ambos m´ etodos tienen sus problemas. el primero est´ a apuntado por p y q y el segundo por r.) liberan autom´ aticamente la memoria que no se sigue utilizando.Organizaci´ on y gesti´ on de la memoria 11 5.3. Supongamos que tenemos dos bloques de memoria.1. Grosso modo hay dos t´ ecnicas b´ asicas de liberaci´ on autom´ atica de memoria: uso de contadores de referencia y t´ ecnicas de marcado. la situaci´ on cambia a: p 1 q 2 r Tras la asignaci´ on p:= q. Liberaci´ on autom´ atica Ciertos lenguajes (Java. Imaginemos que tenemos una lista circular: p 2 1 Si hacemos que p apunte a NIL. Los contadores de referencia estar´ ıan as´ ı: p 2 q 1 r Si se produce la asignaci´ on q:=r.3. etc.

procedure escribe. como APL o algunos dialectos de LISP.). 6. writeln(i).1.2. escribe. la llamada a writeln siempre hace referencia a la variable global i. observa la diferencia de resultados en el siguiente programa: program dinamico. la detenci´ on peri´ odica del programa puede resultar muy molesta para el usuario. enlaces. etc. sin entrar en demasiados detalles. Subrutinas con par´ ametros o variables locales de tama˜ no variable La t´ ecnicas estudiadas reservan en la pila una cantidad de memoria fija cuando se llama a una subrutina. var i: integer. ´ Ambito din´ amico Hasta el momento hemos considerado la organizaci´ on de la memoria en lenguajes de programaci´ on con ´ ambito l´ exico. con ´ ambito din´ amico. El principal problema de estas t´ ecnicas es que si no se implementan con suficiente eficiencia. finalmente.2. Pero hay lenguajes. Por ejemplo. a continuaci´ on. mini. lo que ocupan las variables locales y lo que ocupan los campos propios del registro de activaci´ on (direcci´ on de retorno. algunas de estas caracter´ ısticas. escribe. 6. begin i := 2. lenguajes en los que las referencias s´ olo pueden resolverse en tiempo de ejecuci´ on. Sin embargo. 6. Otros aspectos de las subrutinas Ciertas caracter´ ısticas de lenguajes de programaci´ on modernos pueden complicar bastante la gesti´ on de la memoria asociada a las subrutinas. T´ ecnicas de marcado Con las t´ ecnicas de marcado. cada cierto tiempo se detiene la ejecuci´ on del programa de usuario y se ejecuta una rutina que marca todos los bloques como “libres” para. es decir. . var i: integer. end. lenguajes en los que podemos resolver la referencia a par´ ametros o variables analizando u ´nicamente el programa fuente. la llamada a escribe dentro de mini hace que la i del writeln sea la local de mini. Con ´ ambito l´ exico 2 2 Con ´ ambito din´ amico 2 1 Utilizando el ´ ambito l´ exico. De todas formas. los bloques que han quedado marcados como “libres” se liberan. Implementar correctamente el ´ ambito din´ amico supone mantener en la pila informaci´ on con los nombres de las variables y recorrerla o bien emplear una estructura separada. procedure mini.12 II26 Procesadores de lenguaje 5. con ´ ambito din´ amico. es decir. la implementaci´ on eficiente es muy compleja. end. El tama˜ no de este trozo de memoria puede determinarse en tiempo de compilaci´ on. i := 1. recorrer todos los punteros del programa para ir marcando como “utilizados” los bloques apuntados. Se puede aliviar el problema en parte mediante el empleo de threads paralelos que se encarguen del marcado y liberaci´ on. y es el resultado de sumar lo que ocupan los par´ ametros. end.3. Comentaremos brevemente.

6. Por ejemplo. Entonces es pr´ acticamente obligatorio a˜ nadir el par´ ametro ficticio. } return a. Finalmente. Casi todos los problemas vienen del hecho de que la subrutina anidada puede acceder a variables locales de la subrutina en la que est´ a. 6. dentro(n-1). podemos a˜ nadir un nuevo par´ ametro a dentro que contenga la direcci´ on de a. Subrutinas anidadas El poder anidar subrutinas introduce numerosas complicaciones. Como hemos comentado. en pseudo-C podr´ ıamos tener la siguiente definici´ on de la funci´ on suma: int suma(int a. no podemos aplicar directamente la estrategia propuesta.4.3. Otra posibilidad es mantener una estructura separada (llamada display ). Por ejemplo.Organizaci´ on y gesti´ on de la memoria 13 Si nuestro lenguaje admite par´ ametros o variables locales de tama˜ no desconocido en tiempo de compilaci´ on (por ejemplo. el problema es mucho m´ as complejo: podemos intentar acceder a la variable a mucho despu´ es de que fuera haya dejado de existir. int b) { return a+b. Si adem´ as las funciones se pueden devolver como resultado o se pueden almacenar en variables. c Universitat Jaume I 2006-2007 . En estos casos.1). Dado que dentro es recursiva. especialmente si se asocia con la capacidad de pasar subrutinas como par´ ametro o devolverlas como resultado. La manera de tratar esto es reservando con cada funci´ on parcialmente parametrizada una lista con los par´ ametros ya instanciados. vectores con rango de ´ ındices acotado por variables). la cosa se complica un poco m´ as si adem´ as podemos pasar funciones como par´ ametros. Hay varias maneras de resolver esto. Parametrizaci´ on parcial La parametrizaci´ on parcial (tambi´ en llamada currying ) permite definir nuevas funciones especificando el valor de alguno de los argumentos de otras funciones ya existentes. int dentro(int n) { if (n> 1) { a++. Una soluci´ on es representar los objetos de tama˜ no desconocido en tiempo de compilaci´ on mediante punteros a memoria gestionada din´ amicamente: los punteros s´ ı tienen tama˜ no conocido en tiempo de compilaci´ on. hay que recurrir a reservar din´ amicamente memoria para almacenar los objetos que son accesibles desde las funciones devueltas. } Y despu´ es podr´ ıamos definir la funci´ on incrementa de forma similar a: incrementa= suma(. } El problema aqu´ ı es c´ omo conseguir acceder desde dentro al registro de activaci´ on de fuera. Una es a˜ nadir un campo m´ as al registro de activaci´ on de modo que cada subrutina sepa d´ onde est´ a su padre. no podemos saber a qu´ e “distancia” est´ a a. sean las funciones (en pseudo-C): int fuera(int m) { int a. } dentro(m).

otros. Parametrizaci´ on parcial. • T´ ecnicas autom´ aticas: contadores de referencia. • Limitada. • Sencilla. • Memoria gestionada en tiempo de compilaci´ on. Memoria din´ amica: • Gesti´ on m´ as compleja. .14 II26 Procesadores de lenguaje 7. • Registros de activaci´ on: par´ ametros. Resumen Tres tipos de memoria: • C´ odigo. Anidamiento. Memoria est´ atica: • Se gestiona en tiempo de compilaci´ on. variables locales. Par´ ametros y variables de tama˜ no variable. Aspectos interesantes de las subrutinas: • • • • ´ Ambito din´ amico. • Memoria gestionada en tiempo de ejecuci´ on: pila y heap. t´ ecnicas de marcado. Memoria de pila: • Para la informaci´ on de las subrutinas.