Sunteți pe pagina 1din 42

Programming Language Concepts 2/E

Ghezzi-Jazayeri

Captulo 3: La estructura de los LP.


Un LP es una notacin formal para describir algoritmos para ejecutar en una computadora. Como todas las notaciones formales, un LP tiene 2 componentes principales: sintaxis y semntica. La sintaxis: es el conjunto de reglas que especifican la composicin de los programas a partir de letras, dgitos, y otros caracteres. Por ejemplo, las reglas sintcticas podran especificar que cada parntesis abierto debe igualarse a un parntesis cerrado en una expresin aritmtica, y que dos sentencias cualquiera deben estar separadas por un punto y coma. Las reglas semnticas especifican el significado de cualquier programa vlido sintcticamente escrito en el lenguaje. Tal significado puede ser expresado mapeando cada construccin del lenguaje en un dominio cuya semntica sea conocida. Por ejemplo: una forma de describir la semntica de un lenguaje es dando una descripcin de cada construccin del lenguaje en ingls (o en castellano). Tal descripcin, por supuesto, sufre de la informalidad y ambigedad de los lenguajes naturales pero brinda una visin razonablemente intuitiva del lenguaje. En este captulo tomaremos el enfoque operacional para la semntica, es decir, la semntica de un LP ser descripta especificando el comportamiento de un procesador abstracto que ejecuta programs escritos en el lenguaje. Esta caracterizacin semntica de un lenguaje puede ser presentada usando una notacin formal y rigurosa. Sin embargo, nosotros seguiremos un enfoque ms tradicional e informal, porque es entendido ms fcil e intuitivamente por los programadores y provee una visin de alto nivel de los problemas encontrados en la implementacin del lenguaje.

3.1. Definicin del lenguaje. Cuando Ud. lee un programa, cmo sabe qu significa? Cmo hace un compilador para saber cmo traducir un programa? Cualquier lenguaje de programacin debe ser definido con suficiente detalle para permitir resolver esta clase de cuestiones. Ms especificamente, una definicin de un lenguaje debera permitir a una persona o una computador (programa) determinar: 1. Si un programa es de hecho vlido. 2. Si el programa es vlido, cual es su significado o efecto. En general, deben definirse dos aspectos de un lenguaje de programacin (o de un lenguaje natural): sintaxis y semntica. 3.1.1. Sintaxis: Las reglas de sintaxis definen la forma del lenguaje: describen como las sentencias pueden ser formadas como secuencias de componentes simples llamados smbolos. Usando estas reglas podemos determinar si una sentencia es legal o no. La sintaxis no nos dice nada acerca del contenido (o significado), esa es la tarea de las reglas semnticas. Por ejemplo: los smbolos del lenguaje Pascal son las palabras claves (begin, end, while, etc.), los identificadores, nmeros, operadores, etc. La sintaxis de Pascal nos dice como Captulo 3: La estructura de los LP Pgina 1

Programming Language Concepts 2/E combinar tales smbolos para formar sentencias legales.

Ghezzi-Jazayeri

El conjunto de caracteres que constituyen el alfabeto del lenguaje y la forma en que tales caracteres pueden ser combinados para formar smbolos vlidos estn especificados por las reglas lxicas del lenguaje. Por ejemplo: Pascal considera las maysculas y minsculas idnticas, pero C las considera distintas. As, de acuerdo a las reglas lxicas, Memoria y memoria se refieren a la misma variable en Pascal, pero a variables diferentes en C. Las reglas lxicas tambin nos cuentan que <> es un operador vlido en Pascal pero no en C, donde se utiliza != para el mismo operador. La distincin entre reglas sintcticas y lxicas algunas veces es arbitraria. Ambas contribuyen a la apariencia externa de un lenguaje. Usaremos los trminos sintaxis y reglas sintcticas en un sentido amplio, incluyendo en estos trminos las cuestiones lxicas. Cmo describimos la sintaxis de un lenguaje? Como hay un nmero infinito de programas legales e ilegales en cualquier lenguaje til, claramente no podemos enumerarlos a todos.Necesitamos una forma de definir un conjunto infinito usando una descripcin finita. FORTRAN fue definido estableciendo simplemente algunas reglas en ingls. ALGOL 60 se defini con una gramtica libre de contexto desarrollada por John Backus. El mtodo se conoce como BNF o Backus Naur Form (Peter Naur fue el editor del reporte del ALGOL 60) y nos permite tener una definicin compacta y clara de la sintaxis de un lenguaje. 3.1.1.1 BNF. BNF es una sintaxis de metalenguaje. Un metalenguaje es un lenguaje que es usado para describir otros lenguajes. El lenguaje ingls establece que una oracin puede consistir de un sujeto, verbo y un objeto seguido de un punto como se describe en BNF de la siguiente forma: <oracin> <sujeto> <verbo> <objeto> . Los smbolos '<', '>', y '' son smbolos del metalenguaje y no del lenguaje que est siendo descrito. El smbolo significa 'est definido como'. Las entidades dentro de los parntesis '<' y '>' se conocen como no terminales; y una entidad tal como el punto se conoce como terminal. Para un lenguaje los terminales son lo que anteriormente definimos como smbolos mientras que los no terminales son entidades lingusticas que representan conjuntos de strings de smbolos. Las reglas sintcticas establecen que subconjunto de estos strings son vlidos. En la regla anterior el no terminal <oracin> aparece del lado izquierdo y est definido por la definicin del lado derecho de la regla (cada cosa que sigue al ). Los no terminales del lado derecho, deben ser definidos con otras reglas BNF; y los terminales deben ser smbolos vlidos del lenguaje. Una descripcin sintctica completa de un lenguaje -llamada una gramtica- consiste de un conjunto de tales reglas donde queda definido cada uno de los no terminales. Un no terminal particular se establece como el smbolo de inicio. En los LP, ste es generalmente el no terminal con el nombre <programa>. Captulo 3: La estructura de los LP Pgina 2

Programming Language Concepts 2/E

Ghezzi-Jazayeri

Un string consistente de slo smbolos terminales es una sentencia vlida en el lenguaje si este puede ser derivado (o generado) partiendo del smbolo de inicio. Derivamos un string a partir del smbolo de comienzo reemplazando el smbolo de comienzo con una de sus definiciones. Esto genera un string que consiste de terminales y no terminales. Cada no terminal en el string luego se reemplaza por sus definiciones, y este paso se repite hasta que no queden no terminales sin reemplazar y tengamos un string consistente slo de smbolos terminales. Por ejemplo, podramos completar nuestra gramtica del lenguaje ingls con las siguientes reglas adicionales: <verbo > --> see hit grab <sujeto> --> I we <objeto> --> him her you y asumimos que <sentencia> es el smbolo inicial de la gramtica. El smbolo se lee como 'o' y es usado para proveer definiciones alternativas para un mismo no terminal. Dada esta gramtica, podemos derivar la sentencia 'I hit you.' aplicando las siguientes derivaciones (usaremos el smbolo para separar los diferentes pasos): <sentencia> <sujeto> <verbo> <objeto>. I <verbo> <objeto>. I hit <objeto>. I hit you. Observe que en cada paso de la derivacin, debemos elegir cual ser el prximo no terminal a ser reemplazado. En este ejemplo de derivacin, hemos reemplazado siempre el no terminal ms a la izquierda. Sin embargo podramos haber derivado el mismo string con los siguientes pasos: <oracin> <sujeto> <verbo> <objeto>. <sujeto> hit <objeto>. <sujeto> hit you. I hit you. El proceso de derivacin puede ser modelado mejor con un rbol de derivacin, donde el smbolo de inicio es la raz, y los terminales del string son las hojas. Nuestras dos derivaciones pueden ser mostradas con el siguiente rbol:
< oracin > <s ujeto> I <verbo> hit <objeto> you .

Podemos decir que un string de smbolos es vlido en el lenguaje si podemos construir un rbol de derivacin para l. El rbol de derivacin tambin es llamado parse tree. Derivar el rbol de derivacin se llama parsing. La tarea de determinar si un string es vlido se llama reconocimiento. Como hemos visto, el reconocimiento puede ser logrado mediante el parsing. El parsing es un paso esencial del procesamiento de lenguajes, y existen muchos algoritmos eficientes de parsing. Las dos clases bsicas de parsers que existen son: parsers top-down y parsers bottom-up; segn la direccin en la cual se construye el rbol de derivacin. Un parser top-down comienza en la raz del rbol y construye el rbol hacia abajo, y un parser bottom-up comienza desde las hojas del rbol y lo construye hacia arriba. Captulo 3: La estructura de los LP Pgina 3

Programming Language Concepts 2/E

Ghezzi-Jazayeri

Un parser bottom-up comienza con un string de smbolos terminales y usa alguna estrategia para repetidamente reemplazar secuencias en el string -correspondientes al lado derecho de alguna regla de la gramtica- con un no terminal -correspondiente al lado izquierdo de la misma regla. Este proceso se repite hasta que el string terminal se reduce al smbolo de comienzo de la gramtica. Un parser top-down usa alguna estrategia para derivar el string terminal dado comenzando desde el smbolo de comienzo de la gramtica, y repetidamente reescribir terminales de acuerdo a estas reglas. Observemos que nuestra gramtica es un ejemplo sobresimplificado del ingls. Se puede hacer una gramtica ms poderosa y ms realista agregando reglas que nos permitan definir un nmero infinito de oraciones en ingls. Pero BNF no es adecuada para describir completamente un lenguaje complicado como el ingls, o cualquier otro lenguaje natural. BNF es capaz de definir gramticas libres de contexto, y los lenguajes naturales no se pueden describir mediante gramticas libres de contexto. Intuitivamente, una GLC puede describir la sintaxis de un lenguaje, si las sentencias vlidas se pueden derivar mediante reemplazos simples de no terminales para su lado derecho. En una G sensible al contexto, en cambio, un no terminal puede ser reescrito usando una regla particular que puede depender del contexto (es decir, de los otros smbolos del string). Las gramticas sensibles al contexto son necesarias para describir los lenguajes naturales. Por ejemplo, cuando derivamos una oracin del ingls, debemos usar does slo si hemos usado un sujeto en tercera persona del singular. Un problema similar surge en la descripcin de los LP. Por ejemplo, en Pascal, el uso de una variable en una expresin debe ser consistente con el tipo de la variable, el cual se estableci en la declaracin de la variable. Una situacin de este tipo puede ser solamente manejada por una gramtica sensible al contexto. Sin embargo, generalmente los LP se definen mediante una BNF estricta. Mediante esta BNF se puede generar un nmero infinito de programas correctos, y un nmero infinito de programa incorrectos (precisamente aquellos que no satisfacen los requerimientos adicionales, sensibles al contexto). Generalmente, los requerimientos sensibles al contexto se verifican luego que el programa ha sido chequeado sintcticamente. A estos requerimientos se los conoce como la 'semntica esttica' del lenguaje. Retornemos a la sintaxis para un ejemplo ms relevante. Consideremos la siguiente gramtica la cual define como formar expresiones aritmticas en un lenguaje hipottico, pero realista: <expresin> identificador (<expresin>) <expresin> <operador> <expresin> <operador> + / * En esta gramtica, identificador se trata como un terminal, pero en forma algo diferente de la utilizada para (. Confiamos en las reglas lxicas para definir un lenguaje. De hecho, entendemos que un identificador puede significar muchos smbolos diferentes. La mayora de los lenguajes define un identificador como una letra seguida de cualquier nmero de letras y dgitos, aunque algunos lenguajes tienen reglas ms complicadas o ms simples. Captulo 3: La estructura de los LP Pgina 4

Programming Language Concepts 2/E

Ghezzi-Jazayeri

En este ejemplo, el no terminal <expresin> es utilizado en su propia definicin. Tales definiciones se llaman recursivas. Estas reglas recursivas permiten que un conjunto finito de reglas defina la estructura de un nmero infinito de programas. En particular, las reglas anteriores para expresiones nos permiten reconocer expresiones vlidas de cualquier longitud. Por ejemplo, la regla: <expresin> ( < expresin> ) nos permite definir un anidamiento arbitrario de una expresin, mientras que la regla: <expresin> <expresin> <operador> <expresin> nos permite combinar un nmero arbitrario de expresiones para formar una nueva expresin. Obviamente, es a travs de la siguiente regla no recursiva: <expresin> identificador que la derivacin desde el no terminal <expresin> puede terminar. Por ejemplo, asumiendo que A es un identificador, las siguientes derivaciones son posibles comenzando desde el no terminal <expresin> <expresin> <operador> <expresin> <expresin> <operador> <expresin> <operador> <expresin> (<expresin>) <operador> <expresin> <operador> <expresin> (A) <operador> <expresin> <operador> <expresin> = ... etc ... 3.1.1.2. Diagramas de sintaxis: La definicin original del Pascal usaba diagramas de sintaxis. Una tcnica grfica para descripcin de sintaxis que es equivalente a BNF, pero ms intuitiva. Los diagramas se pueden observar en la siguiente figura:

Los no terminales se representan con crculos y los terminales por cajas. El smbolo no terminal se define como un diagrama de transicin con una sola entrada y una sola salida. Un string es vlido si puede ser generado atravesando el diagrama de sintaxis desde su extremo inicial hasta su extremo final. En este recorrido, si se encuentra un terminal (caja), dicho smbolo debe estar en el string que est siendo reconocido; si un no terminal (crculo) se encuentra, entonces debe reconocerse dicho no terminal, atravesando el diagrama de transicin para dicho no terminal. Cuando se encuentra una bifurcacin en el camino, slo un camino puede ser elegido. Los diagramas de sintaxis son lo suficientemente similares a la BNF para permitirle entender las reglas. En conclusin, la descripcin sintctica de un lenguaje tiene dos usos primarios: a) Ayudar al programador, para que sepa como escribir un programa sintcticamente correcto. (Derivacin). Por ejemplo, si uno est inseguro acerca de la sintaxis de la Captulo 3: La estructura de los LP Pgina 5

Programming Language Concepts 2/E

Ghezzi-Jazayeri

sentencia if-then-else, una vista en la BNF o en los diagramas de sintaxis puede eliminar rpidamente cualquier duda. b) Para determinar si un programa es sintcticamente correcto. Esto es exactamente lo que hace un compilador. El escritor del compilador usa la gramtica para escribir un analizador sintctico o parser que es capaz de reconocer todos los programas vlidos (Reconocimiento). Este procesador est ampliamente automatizado. De hecho, hay programas (generadores de compiladores) que usan la gramtica del lenguaje como entrada y producen el analizador como salida. LEX y YACC son dos herramientas de UNIX muy conocidas que generan analizadores sintcticos y lxicos, respectivamente, comenzando a partir de una descripcin de las reglas lxicas y sintcticas del lenguaje. 3.1.2. Semntica. Las reglas semnticas de un lenguaje definen el significado de las sentencias vlidas en ese lenguaje. Por ejemplo: la semntica del Pascal nos ayuda a determinar que la sentencia: var s: set of (red, white, blue) causa que el compilador reserve espacio para una variable llamada s, y adems, que los valores que esta variable puede mantener, estn restringidos a los elementos del conjunto potencia del conjunto {red, white, blue}. Otro ejemplo, la semntica de C establece que la sentencia: if (a>b) then max=a; else max=b; significa que la expresin a>b debe ser evaluada, y dependiendo de su valor, se ejecute una de las dos sentencias de asignacin. Observe que las reglas sintcticas nos dicen como formar esta sentencia-por ejemplo, donde poner un ;- y las reglas semnticas nos dicen cual es el efecto de la sentencia. Aunque los diagramas de sintaxis y BNF son herramientas estndares para la descripcin sintctica, ninguno de ellos ha sido ampliamente aceptado como estndar para la descripcin semntica. Existen muchos enfoques diferentes para la definicin semntica (ver Cap. 9), pero ninguno es enteramente satisfactorio. Nosotros tomaremos en este captulo, y a lo largo del libro, un enfoque operacional para describir la semntica de los lenguajes de programacin. Observaremos el comportamiento de un procesador abstracto para describir los efectos de cada construccin del lenguaje. Describiremos tal mquina en la siguiente subseccin. Luego describiremos la semntica de las construcciones del lenguaje de programacin en trminos de las operaciones de esta mquina. Nuestra mquina es abstracta, esto significa que no es una mquina real tal como la IBM 370 o la HP 3000.. Est diseada para mostrar simplemente, los requerimientos de los LP, ms que para ejecutar eficientemente. Tambien puede ser usada como un modelo para implementacin de lenguajes, en el sentido de que uno puede derivar de forma sencilla, Captulo 3: La estructura de los LP Pgina 6

Programming Language Concepts 2/E

Ghezzi-Jazayeri

implementacin simples aplicando los conceptos que discutiremos aqu. Las implementaciones resultantes, sin embargo, probablemente sern ineficientes. Para lograr eficiencia, cualquier implementacin real, tendr que diferir del modelo en varias formas, por ejemplo, en como son organizadas y accedidas las estructuras de datos. El propsito del modelo es simplemente establecer los efectos del lenguaje, dando la estructura de la mquina abstracta. Una implementacin particular del lenguaje en una mquina real dada no est de ninguna manera obligada a implementar la estructura del procesador abstracto usado para definir la semntica del lenguaje; el procesador es slo requerido para implementar los mismos efectos, dando las restricciones y estructura de la implementacin de mquina. Es muy importante separar los temas semnticos del lenguaje de las cuestiones de implementacin. Esto puede hacerse, manteniendo en mente que parte de la descripcin es una descripcin (o restriccin) de la mquina y cul lo es del lenguaje. 3.1.2.1. SIMPLESEM: Un procesador semntico abstracto simple. Describiremos la estructura bsica de un procesador abstracto simple que llamaremos SIMPLESEM. A medida que vayamos necesitando ms caractersticas de SIMPLESEM para construcciones semnticas ms complicadas, las iremos definiendo. SIMPLESEM nos permite definir y entender la semntica de las construcciones de los LP. En su forma bsica, SIMPLESEM consiste de un puntero de instruccin y una memoria. La memoria se divide en secciones separadas para el cdigo y los datos. El puntero de instruccin siempre apunta a una posicin en la memoria de cdigo que contiene una instruccin, lo denominaremos ip. A las dos secciones de memoria, las reconoceremos como C (cdigo) y D (datos), y a una posicin de memoria como C[i] o D[i]. Usaremos el signo ':=' para significar asignacin. Por ejemplo: ip := ip+1 significa 'incrementar la instruccin apuntada, en 1'. La mquina opera ejecutando los siguientes pasos repetidamente hasta que ejecuta una instruccin especial de stop:

1 Ejecuta la instruccin apuntada por el ip. 2


Si la instruccin no modifica explcitamente el ip, luego el ip se incrementa para apuntar a la prxima instruccin a ser ejecutada (ip:=ip+1).

Para que la mquina arranque, se carga un programa en memoria (mediante un programa cargador) y se hace apuntar ip a la primera instruccin del programa. Supondremos que SIMPLESEM tiene instrucciones para las operaciones usuales como +, -, /, *, >, etc.; requeridas por la mayora de los LP. La figura 3.1 muestra la estructura de SIMPLESEM. A medida que examinemos diferentes clases de lenguajes en las prximas tres subsecciones, completaremos la Captulo 3: La estructura de los LP Pgina 7

Programming Language Concepts 2/E descripcin de SIMPLESEM. ip

Ghezzi-Jazayeri

Memoria de Cdigo

Memoria de Datos (D)

Figura 3.1. Estructura de SIMPLESEM Definiremos la semntica de una construccin de un LP, definiendo la construccin en trminos de operaciones de SIMPLESEM. Este enfoque est basado en la suposicin de que conocemos o entendemos la semntica de nuestra mquina y, por lo tanto, una vez que entendemos el efecto de la construccin en trminos de las operaciones de SIMPLESEM, entenderemos el significado de la construccin. Esto es algo anlogo a definir el significado del verbo italiano pensare como pensar proque asumimos que conocemos el significado de pensar. Observemos que para que este enfoque sea exitoso, es importante mantener la semntica de SIMPLESEM tan simple como sea posible para que podamos concentrarnos en la semntica del LP ms que en las complejidades de la mquina, la cual es simplemente una herramienta de modelado. Podemos ver una mquina como SIMPLESEM de dos formas. Idealmente, uno podra pensar que los programas de lenguajes de alto nivel son almacenados en forma de cdigo fuente, y SIMPLESEM es capaz de decodificar y ejecutar tales instrucciones de alto nivel. Alternativamente, uno puede pensar que el programa fuente se traslada primero a lenguaje de mquina (de ms bajo nivel) de SIMPLESEM. Las dos elecciones pueden tener un efecto equivalente en lo que concierne a la semntica de los lenguajes de alto nivel, pero pueden afectar la eficiencia de la implementacin. Discutiremos este problema en la prxima seccin con respecto a las mquinas reales. 3.2. PROCESAMIENTO DE LENGUAJES: Aunque en teora es posible construir computadoras de propsito especial para ejecutar directamente programas escritos en cualquier lenguaje particular, las computadoras actuales, realmente ejecutan slo programas en un lenguaje de bajo nivel, conocido como lenguaje de mquina. Los lenguajes de mquinas se disean con el objetivo de obtener la mayor velocidad en la ejecucin, por otra parte los lenguajes de programacin a menudo se disean con los objetivos de facilidad de uso y confiabilidad. Un problema bsico, ser, por lo tanto, cmo un programa escrito en un lenguaje de alto nivel, puede ser ejecutado en una computadora cuyo lenguaje de mquina es muy diferente y de nivel ms bajo. Generalmente, tenemos dos alternativas: interpretacin y traduccin. Captulo 3: La estructura de los LP Pgina 8

Programming Language Concepts 2/E

Ghezzi-Jazayeri

3.2.1. Interpretacin. En este caso, las acciones implicadas por las sentencias del lenguaje son ejecutadas directamente. Generalmente, para cada posible accin existe un subprograma (escrito en L. mquina) para ejecutar la accin. As, la interpretacin de un programa, se logra llamando a los subprogramas en la secuencia apropiada. Un intrprete es un programa que repetidamente ejecuta la siguiente secuencia: 1. Toma la siguiente sentencia. 2. Determina las acciones a ser ejecutadas. 3. Ejecuta las acciones. Esta secuencia es muy similar al patrn de acciones llevado a cabo por nuestro procesador modelo SIMPLESEM, el cual describimos a continuacin: 1. Determina la prxima instruccin (la instruccin cuya direccin est apuntada por ip). 2. Avanza el puntero de instruccin (a la direccin calculada en 1). 3. Decodifica la instruccin. 4. Ejecuta la instruccin. Esta similaridad muestra que la interpretacin puede ser vista como una simulacin, sobre una computadora, de una mquina de propsito especial cuyo lenguaje de mquina es el lenguaje de alto nivel. 3.2.2. Traduccin. En este caso, los programas escritos en un lenguaje de alto nivel, son traducidos a una versin equivalente en lenguaje de mquina antes de ser ejecutados. Esta traduccin se realiza en varios pasos. Los subprogramas podran primer ser trasladados a cdigo ensamblador; el cdigo ensamblador se traduce a cdigo de mquina reubicable; las unidades de cdigo reubicable se enlazan juntas en una nica unidad reubicable; finalmente , el programa completo se carga en memoria principal como cdigo de mquina ejecutable. Los traductores usados en cada paso tienen nombres especializados: compilador, ensamblador, linkador, y cargador, respectivamente. En algunos casos, la mquina en la cual se realiza la traduccin (mquina anfitrin) es diferente de la mquina donde se ejecutar el cdigo trasladado (mquina destino). Esta clase de traduccin se llama traduccin cruzada. Los traductores cruzados son la nica solucin viable cuando la mquina destino es demasiado pequea para soportar el traductor. La interpretacin pura y la traduccin pura son los dos extremos de un espectro continuo. En la prctica, muchos lenguajes son implementados por una combinacin de las dos tcnicas. Un programa puede ser traducido a un cdigo intermedio que luego es interpretado. El cdigo intermedio podra ser simplemente una representacin formateada del programa original, que no contiene informacin irrelevante (por ej. Captulo 3: La estructura de los LP Pgina 9

Programming Language Concepts 2/E

Ghezzi-Jazayeri

comentarios y espacios) y con los componentes de cada sentencia almacenados en un formato fijo para simplificar la decodificacin posterior de las instrucciones. En este caso la solucin es bsicamente interpretativa. Alternativamente, el cdigo intermedio podra ser cdigo de mquina (bajo nivel) para una mquina virtual que ms tarde ser interpretada por el software. Esta solucin, la cual descansa mayormente en la traduccin, puede ser adoptada para generar cdigo portable, es decir, cdigo que sea ms fcilmente transferible a mquinas diferentes. En una solucin puramente interpretativa ejecutar una sentencia puede requerir un proceso de decodificacin complicado para determinar las operaciones a ser ejecutadas y sus operandos. En la mayora de los casos este proceso es idntico cada vez que se encuentra la misma sentencia. Por lo tanto, si una sentencia aparece en una parte del programa ejecutada muchas veces (por ejemplo dentro de un loop), la velocidad de ejecucin es fuertemente afectada por este proceso de decodificacin. Por otra parte, la traduccin pura genera cdigo de mquina para cada sentencia de alto nivel; de esta forma el traductor decodifica cada sentencia de alto nivel slo una vez. Las partes usadas frecuentemente son entonces decodificadas muchas veces en su representacin en lenguaje de mquina; como esto se hace eficientemente por hardware, La traduccin pura puede ahorrar tiempo de procesamiento sobre la interpretacin pura. Sin embargo, la interpretacin pura puede ahorrar espacio de almacenamiento, porque en la traduccin pura cada sentencia de alto nivel se expande en decenas o cientos de instrucciones de mquina. En una solucin puramente interpretativa, las sentencias de alto nivel se dejan en su forma original y las instrucciones necesarias para ejecutarlas son almacenadas en un subprograma del intrprete. El ahorro de almacenamiento es evidente si el programa es grande y usa la mayora de las sentencias del lenguaje. Por otra parte, si todos los programas del intrprete se mantienen en memoria principal durante la ejecucin, el intrprete podra gastar espacio para programas pequeos que slo utilizan unas pocas sentencias del lenguaje. 3.3. El concepto de binding (asociacin). Los programas trabajan con entidades (variables, subprogramas, sentencias, etc.), las cuales tienen ciertas propiedades llamadas atributos. Por ejemplo: una variable tiene un nombre, un tipo, un rea de almacenamiento donde se mantiene su valor; un subprograma tiene un nombre, parmetros formales de un cierto tipo, ciertas convenciones de paso de parmetros; una sentencia tiene acciones asociadas. Estos atributos deben ser especificados antes que una entidad sea procesada. Especificar la naturaleza exacta de un atributo se conoce como binding. Para cada entidad, la informacin del binding se mantiene en lo que se conoce como descriptor. El binding es un concepto central en la definicn de la semntica de los lenguajes de programacin. Los lenguajes de programacin difieren en el nmero de entidades con las cuales pueden trabajar, en el nmero de atributos que se pueden otorgar a las entidades y en el momento en el cual se realiza al binding. Algunos atributos pueden ser limitados en tiempo de definicin del lenguaje, otros en tiempo de traduccin del programa (en compilacin) y otros en tiempo de ejecucin del programa (run-time).Por ejemplo, en Ada y FORTRAN, el tipo INTEGER es asociado en tiempo de implementacin y definicin del lenguaje: la definicin del lenguaje establece Captulo 3: La estructura de los LP Pgina 10

Programming Language Concepts 2/E

Ghezzi-Jazayeri

que el tipo INTEGER debe ser soportado y la implementacin del lenguaje determina el conjunto de valores que el tipo INTEGER podr contener.Pascal, por otra parte, permite al programador redefinir el tipo integer, as que el tipo integer se asocia a una representacin en tiempo de traduccin. Se dice que un binding es esttico si est establecido antes del momento de la ejecucin del programa y no puede ser cambiado ms tarde, y que es dinmico si se establece en tiempo de ejecucin y puede ser cambiado de acuerdo a algunas reglas especificadas por el lenguaje. Los conceptos de binding y tiempo del binding ayudan a clarificar muchos aspectos semnticos de los lenguajes de programacin. En la prxima seccin usaremos estos conceptos para ilustrar la nocin de variable. 3.4. VARIABLES. Las computadoras convencionales estn basadas en la nocin de una memoria principal consistente de celdas elementales, cada una de las cuales se identifica por su direccin. Los contenidos de una celda conforman su valor. El valor de una celda puede ser ledo y/o modificado. La modificacin implica el reemplazo de un valor con un nuevo valor. Adems, el hardware permite acceso a las celdas en una forma de 'una a la vez'. Con una pocas excepciones, los lenguajes de programacin pueden ser vistos como abstracciones, a diferentes niveles, del comportamiento de tales computadoras convencionales. En particular ellos introducen la nocin de variables como una abstraccin de la nocin de las celdas de memoria y la nocin de las sentencias de asignacin como una abstraccin de la modificacin destructiva de una celda. En la mayor parte de este y los siguientes captulos restringiremos nuestras consideraciones a los lenguajes de programacin convencionales basados en asignacin. Lenguajes alternativos que soportan estilos de programacin funcin y declarativo sern discutidos en el Cap. 7 y 8. Una variable est caracterizada por un nombre y cuatro atributos bsicos: mbito, tiempo de vida, valor y tipo. El nombre es usado para identificar y referirse a las variables. Algunos lenguajes permiten que las variables no tengan nombres; ejemplos de las llamadas variables annimas se ven ms adelante en este captulo. Tambin se discuten cada uno de los cuatro atributos y las distintas polticas adoptadas por los LP para asociar atributos a variables. 3.4.1. El mbito de una Variable: El mbito de una variable es el rango de las instrucciones del programa, dentro del cual la variable es conocida, y manipuleable. Una variable es visible dentro de su mbito, e invisible fuera de el. Las variables pueden ser limitadas a un mbito en forma dinmica o esttica. El binding esttico de mbito, define el mbito de una variable en trminos de la estructura lxica de un programa, esto es, cada referencia a una variable es estticamente asociada a una declaracin de variable particular (implcita o explcita). Las reglas de mbito esttico son adoptadas por la mayora de los lenguajes tradicionales. El binding dinmico de mbito define el mbito de una variable en trminos de la Captulo 3: La estructura de los LP Pgina 11

Programming Language Concepts 2/E

Ghezzi-Jazayeri

ejecucin del programa. Generalmente, cada declaracin de una variable extiende sus efectos sobre todas las instrucciones ejecutadas a partir de la declaracin, hasta que una nueva declaracin para una variable con el mismo nombre sea encontrada. APL, LISP, y SNOBOL4 son ejemplos de lenguajes con reglas de mbito dinmicas. Las reglas de mbito dinmicas son ms fciles de implementar pero tienen desventajas en trminos de disciplina de programacin y eficiencia de la implementacin. Los programas son difciles de leer porque la identificacin de la declaracin particular a la cual una variable dada se asocia depende del punto particular de ejecucin, y no puede ser determinado estticamente. 3.4.2. El tiempo de vida de una variable. El tiempo de vida, o extensin de una variable es el intervalo de tiempo en el cual un rea de almacenamiento se asocia a la variable. Este rea es usada para mantener el valor de la variable. Usaremos el trmino objeto de datos (o simplemente objeto) para denotar conjuntamente el almacenamiento y el valor. La accin por la cual se reserva un rea de almacenamiento para una variable, se llama alocacin. En algunos lenguajes, la alocacin es realizada antes del tiempo de ejecucin (alocacin esttica). En otros lenguajes, la alocacin se realiza en tiempo de ejecucin (alocacin dinmica) ya sea mediante pedidos explcitos del programador mediante una sentencia de creacin o automticamente al entrar al mbito de la variable. 3.4.3. El valor de una variable. El valor de una variable est representado en forma codificada en el rea de almacenamiento asociada a la variable. La representacin codificada es luego interpretada de acuerdo al tipo de la variable. En algunos LP el valor de una variable puede ser una referencia (puntero) a un objeto. En tales lenguajes, un objeto puede ser hecho accesible va una cadena de referencias (o camino de acceso) de longitud arbitraria. Dos variables comparten un objeto si cada una tiene un camino de acceso al objeto. Un objeto compartido y modificado a travs de un camino de acceso, hace la modificacin conocida para todos los posibles caminos de acceso. Compartir objetos se usa para ahorrar almacenamiento, pero puede llevar a que los programas sean difciles de leer, porque el valor de las variables puede ser modificado an cuando ellas no sean referenciadas. Las referencias son el medio principal para acceder a variables annimas. La asociacin entre una variable y el valor mantenido en su rea de almacenamiento es generalmente dinmica: el valor puede ser modificado por una operacin de asignacin. Una asignacin tal como b:=a causa que una copia del valor de a sea guardada en el rea de almacenamiento asociada a b. Algunos lenguajes permiten congelar el binding entre una variable y su valor una vez que se ha establecido. La entidad resultante se conoce como una constante simblica definida por el usuario. Por ejemplo en Pascal: Captulo 3: La estructura de los LP Pgina 12

Programming Language Concepts 2/E const pi =3.1416 y en ALGOL 68 real pi=3.1416 y luego se puede usar pi en expresiones como: circunferencia:=2*pi*radio

Ghezzi-Jazayeri

La variable pi se asocia al valor 3.1416 y su valor no puede ser cambiado, es decir que el traductor reportar un error si se intenta hacer una asignacin a pi. Pascal y Algol 68 difieren en el momento en el cual se realiza el binding entre la variable (constante simblica) y su valor. En Pascal el valor es un nmero o un string de caracteres, y es posible establecer el binding en tiempo de traduccin. El traductor puede legalmente sustituir el valor de la constante por su nombre simblico en el programa. En Algol 68 el valor puede ser dado como una expresin que involucre otras variables y constantes: por lo tanto el binding puede ser solamente establecido en tiempo de ejecucin, cuando la variable sea creada. Una constante manifiesta es una constante simblica cuyo valor puede ser limitado asociado en tiempo de traduccin. Una cuestin siempre presente cuando se habla de binding es: Cul es el valor de la variable inmediatamente despus de que esta es creada? Hay un nmero de posibles enfoques. Desafortunadamente, la mayora de las especificaciones de los lenguajes no especifican la respuesta a esta cuestin, y por lo tanto existen distintas implementaciones del mismo lenguaje que resuelven el problema de diferentes formas. Este hecho hace dificultoso probar que el programa es correcto, porque la correccin del programa puede depender de la implementacin. Adems, llevar un programa aparentemente correcto a una instalacin diferente puede producir errores no previstos o resultados inesperados. Una solucin obvia y frecuentemente adoptada es ignorar el problema. En este caso el string de bits que se encuentra en el rea de almacenamiento asociada con la variable se considera su valor inicial. Otra solucin es proveer una estrategia de inicializacin definida por el sistema: por ejemplo, los enteros son inicializados a cero, los caracteres a blancos, etc. Otra solucin consiste en ver una variable no inicializada como inicializada con un valor especial no inicializado y evitar cualquier acceso de lectura para tales variables hasta que se le asigne un valor significativo. Esta solucin, que es la ms clara, puede ser lograda de diversas maneras. Su nica contra puede ser el costo asociado con los chequeos en tiempo de ejecucin necesarios para asegurar que un valor no inicializado nunca sea usado en el programa. 3.4.4. El tipo de una variable. El tipo de una variable puede ser visto como una especificacin de la clase de valores que pueden ser asociados con la variable, junto con las operaciones que pueden ser legalmente usadas para crear, acceder y modificar tales valores. Cuando se define el lenguaje, un nombre de tipo es generalmente asociado a una cierta Captulo 3: La estructura de los LP Pgina 13

Programming Language Concepts 2/E

Ghezzi-Jazayeri

clase de valores y un conjunto de operaciones. Por ejemplo: el tipo boolean se asocia a los valores true y false y a las operaciones and, or y not. Los valores y las operaciones son asociados a una cierta representacin de mquina cuando se implementa el lenguaje. Por ejemplo true podra ser asociado al string de bits 00...001, false a 00...000, y las operaciones and, or y not podran ser implementadas va instrucciones de mquina apropiadas que operen sobre los strings de bits que representan a los booleanos. En algunos lenguajes, el programador puede definir nuevos tipos por medio de una declaracin de tipo. Por ejemplo, en Pascal uno puede escribir: type t = array [1..10] of boolean Esta declaracin establece una asociacin (en tiempo de traduccin) entre el tipo llamado t y su implementacin (un arreglo de 10 booleanos, cada uno accesible va un ndice en el subrango 1 a 10). Como consecuencia de este binding, el tipo t hereda todas las operaciones de la representacin de la estructura de datos (el arreglo); as, es posible, leer y modificar cada uno de los componentes de un objeto de tipo t mediante indexado dentro del arreglo. En lenguajes que soportan la definicin de tipos de datos abstractos no hay un binding por default entre un nuevo tipo y el conjunto de operaciones; las operaciones deben ser especificadas como un conjunto de subprogramas en la declaracin del nuevo tipo. La declaracin del nuevo tipo tiene la siguiente forma general. type t = estructura de datos que representa los objetos de tipo t; procedimientos a ser usados para manipular los objetos de datos de tipo t end; Los tipos pueden ser asociados a variables dinmica o estticamente. La solucin esttica es adoptada por la mayora de los lenguajes tradicionales, tales como FORTRAN, ALGOL 60, COBOL, Pascal, ALGOL 68, SIMULA 67, CLU y Ada. En estos lenguajes, el binding entre una variable y su tipo es generalmente especificado por una declaracin de variables. Por ejemplo, en Pascal uno puede escribir: var x,y : integer; z : boolean; Sin embargo, en algunos lenguajes (tales como FORTRAN) la primer ocurrencia de un nuevo nombre de variable se toma como una declaracin implcita. La ventaja de las declaraciones explcitas est en la claridad de los programas y en su confiabilidad, porque errores ortogrficos en los nombres de variables pueden ser fcilmente detectados. Por ejemplo, en FORTRAN la declaracin de la variable ALPHA, seguida de una sentencia tal como ALPA=7.3, que intenta asignarle un valor, no sera detectada como un error; ya que se considera como la declaracin implcita de una nueva variable (ALPA). Observe que la cuestin de declaraciones de tipo implcito no es una cuestin semntica. Semnticamente, Pascal y FORTRAN son equivalentes con respecto al tipado de las variables porque ambos asocian variables a tipos en tiempo de traduccin. FORTRAN tiene reglas por default para determinar el binding particular, pero el tiempo del binding es el mismo en los dos lenguajes. Captulo 3: La estructura de los LP Pgina 14

Programming Language Concepts 2/E

Ghezzi-Jazayeri

APL y SNOBOL4 son dos lenguajes que usan un binding dinmico entre variables y tipos. Por ejemplo, en APL un nombre de variable puede significar en diferentes momentos de la ejecucin de un programa una variable simple, un arreglo de una dimensin, un arreglo multidimensional o quizs una etiqueta. En APL, las variables no son declaradas, sino que su tipo es implcitamente determinado por el valor que actualmente contienen. Por ejemplo, luego de la ejecucin de la sentencia: A5 A es una variable entera que contiene 5 como valor; una sentencia posterior: A tratara a A como una variable etiqueta y saltara a la sentencia cuyo nmero es el valor que contiene A. Todava mas tarde, A podra ser modificada por la siguiente sentencia: A 1 2 51 0 Y ahora se convertira en un arreglo unidimensional. El lmite inferior del ndice es seteado implcitamente a 1. El binding dinmico provee gran flexibilidad en la creacin y manipulacin de las estructuras de datos. Sin embargo, tiene desventajas en trminos de disciplina en la programacin, 'correctitud' de los programas y eficiencia de la implementacin. Los programas son difciles de leer porque el tipo de la variable que aparece en una sentencia no puede conocerse inmediatamente, porque depende del camino que sigui la ejecucin del programa. Como consecuencia, en APL una sentencia como: A[2;3] 0 intentando asignar cero a la componente en la fila 2, columna 3, de un arreglo bidimensional, es correcta slo si, en un punto particular de la ejecucin, A es un arreglo bidimensional. Por ejemplo, sera incorrecta si A 0 fuera la ltima asignacin hecha a A. En otras palabras, APL requiere un chequeo de tipos dinmico para verificar que el uso de cada variable sea consistente con su tipo. En contraste, el binding esttico es la base del chequeo de tipos esttico, cuyos beneficios a la correccin de los programas fueron discutidos en la seccin 2.4. Como otro ejemplo, considere la sentencia APL: A B+C Esta sentencia es correcta si una o ambas de las variables B y C son variables simples, pero adems si B y C son arreglos con el mismo nmero y tamao de dimensiones. Mas an, las acciones necesarias para ejecutar esta sentencia dependen de los tipos de B y C. Si B y C son variables simples, las acciones implicadas son una simple suma y una asignacin, si B y C son arreglos de dos dimensiones, las acciones implicadas comprenden un loop de adiciones y asignaciones. Captulo 3: La estructura de los LP Pgina 15

Programming Language Concepts 2/E

Ghezzi-Jazayeri

Este ejemplo muestra que la informacin acerca de los tipos de las variables APL debe ser usadas en tiempo de ejecucin no slo para realizar chequeos de tipo dinmico, sino para elegir las acciones apropiadas para ejecutar sentencias. Para usar la informacin de tipo, los descriptores deben existir en tiempo de ejecucin y deben ser modificados cada vez que un nuevo binding es establecido. Esta es precisamente la razn por la que otros lenguajes (como Pascal) son diseados para que la informacin de tipo sea conocida en tiempo de traduccin. Consecuentemente, los descriptores necesitan existir solo durante ese tiempo. Los lenguajes de programacin que adoptan el binding dinmico entre variables y tipos son procesados en forma ms natural por interpretacin. Como muestra el ejemplo, no hay generalmente informacin suficiente antes del tiempo de ejecucin para generar cdigo para la evaluacin de expresiones que involucren variables de tipo desconocido. La eleccin entre traduccin e interpretacin en la implementacin de un lenguaje est fuertemente influenciada por las reglas de binding entre variables y tipos. Los lenguajes con binding dinmico estn orientados a la interpretacin, mientras que los lenguajes con binding esttico estn orientados a la traduccin. 3.5. UNIDADES DE PROGRAMAS Los LP permiten que un programa est compuesto por un nmero de unidades. Las unidades de programa pueden ser desarrolladas en una forma ms o menos independiente y algunas veces pueden ser traducidas separadamente y combinadas luego de la traduccin. Las variables declaradas dentro de una unidad son locales a la unidad. Una unidad puede ser activada en tiempo de ejecucin. Ejemplos de unidades son los subprogramas en lenguaje de mquina, las subrutinas de FORTRAN y los procedimientos y bloques de ALGOL 60. La prxima seccin repasa algunos mecanismos elementales que controlan el flujo de la ejecucin entre unidades de programa y los bindings establecidos cuando se activa una unidad. El paso de parmetros a subprogramas se discute en la seccin 3.7. La representacin de una unidad de programa durante la ejecucin se llama instancia de la unidad y est compuesta de un segmento de cdigo y un registro de activacin. El segmento de cdigo tiene contenido fijo (las instrucciones de la unidad) y est almacenado en la memoria de instrucciones de SIMPLESEM. Los contenidos del registro de activacin pueden cambiar y estn almacenados en la memoria de datos. El registro de activacin contiene toda la informacin necesaria para ejecutar la unidad, incluyendo los objetos de datos asociados con las variables locales de una instancia de unidad particular. La posicin relativa de un objeto de datos en el registro de activacin se conoce como su desplazamiento offset. Las unidades pueden o no tener nombre. Los procedimientos en Pascal y C son ejemplos de unidades con nombre; los bloques en ALGOL 60 y C son ejemplos de unidades no nombradas. Una unidad no es una pieza de programa completamente autocontenida e independiente. Si es un subprograma, puede ser activado por una llamada al subprograma ejecutada por otra unidad, a la cual retorna el control luego de la ejecucin. Por lo tanto el punto de retorno es una pieza de informacin (variable) que debe ser guardada en el registro de activacin del subprograma en el momento de la invocacin al mismo. Adems las unidades pueden referenciar variables (variables no locales) distintas de Captulo 3: La estructura de los LP Pgina 16

Programming Language Concepts 2/E

Ghezzi-Jazayeri

aquellas declaradas localmente, si las reglas de mbito del lenguaje lo permiten. Las variables no locales que pueden ser referenciadas por cualquier unidad del programa se conocen como variables globales. El ambiente de referencia de una instancia de unidad U consiste en las variables locales de U, las cuales son asociadas a objetos almacenados en el registro de activacin de U (ambiente local) y en las variables no locales de U, las cuales son asociadas a objetos almacenados en los registros de activacin de otras unidades (ambiente no local). Dos variables del ambiente de referencia de una unidad que denotan el mismo objeto de datos son llamadas alias. La modificacin de un objeto de datos asociado a una variable no local se llama efecto colateral. Las unidades pueden a menudo ser activadas recursivamente, es decir que una unidad puede llamarse a s misma ya sea, directa o indirectamente a travs de otra unidad. En otras palabras, una nueva activacin puede ocurrir antes de la terminacin de una activacin previa. Todas las instancias de la misma unidad estn compuestas del mismo segmento de cdigo pero diferentes registros de activacin. As, en presencia de recursin, el binding entre un registro de activacin y su segmento de cdigo es necesariamente dinmico. Cada vez que una unidad se activa, debe establecerse un binding entre un registro de activacin y su segmento de cdigo para formar una nueva instancia de unidad. Algunos lenguajes como el FORTRAN, no soportan la activacin recursiva de unidades, es decir que permiten un mximo de una instancia para cada unidad. As, el binding entre el segmento de cdigo y el registro de activacin puede ser esttico, y la creacin (e inicializacin) de los objetos de datos para las variables locales de una unidad pueden ser hechos antes de la ejecucin del programa. 3.6. ESTRUCTURA EN TIEMPO DE EJECUCIN DE LOS LP. De acuerdo a su comportamiento en tiempo de ejecucin podemos identificar tres clases de lenguajes. Esta es una clasificacin semntica y usaremos SIMPLESEM para explicar las diferencias entre las clases. Lenguajes estticos: Ejemplificados por FORTRAN y COBOL, estos lenguajes garantizan que los requerimientos de memoria para cualquier programa pueden ser determinados antes de que la ejecucin del programa comience. Por lo tanto, toda la memoria necesitada puede ser reservada antes de la ejecucin del programa. Obviamente estos lenguajes no permiten recursin, porque la recursin podra requerir un nmero arbitrario de instancias de unidades y as los requerimientos no podran ser determinados en tiempo de traduccin. Lenguajes basados en pila: Estos lenguajes, ejemplificados por ALGOL 60, permiten programas ms potentes cuyos requerimientos de memoria no pueden ser calculados en tiempo de traduccin. Sin embargo, el uso de la memoria es predecible y sigue una disciplina LIFO; por lo tanto en SIMPLESEM usamos una pila para modelar su comportamiento (ver seccin 3.6.2). Una vez ms, observemos que una implementacin particular de estos lenguajes no necesitara usar una pila. Nosotros usamos una pila para nuestro modelo porque es suficiente y su semntica es conocida. Un stack no es parte de Captulo 3: La estructura de los LP Pgina 17

Programming Language Concepts 2/E la semntica del lenguaje, es parte de nuestro modelo semntico.

Ghezzi-Jazayeri

Lenguajes dinmicos: Estos lenguajes, como el nombre lo indica, tienen un uso de memoria impredecible. Ejemplificados por LISP, PROLOG, APL y SNOBOL 4, no pueden ser modelados por un SIMPLESEM basado en pila porque ellos permiten al programador crear objetos de datos en puntos arbitrarios durante la ejecucin del programa. La mayora de los lenguajes actuales (Ada, Pascal, C, ALGOL 68) son principalmente basados en pila, incluyendo algunas caractersticas dinmicas. En la prxima seccin examinaremos estas clases ms profundamente. 3.6.1. La estructura del FORTRAN. Un programa FORTRAN est compuesto de un conjunto de unidades: un programa principal (main) y un conjunto (posiblemente vaco) de subprogramas (subrutinas y funciones). El monto de almacenamiento requerido para guardar cada variable local es fijo, y es conocido en tiempo de traduccin y no puede ser cambiado durante la ejecucin de la unidad. Cada unidad se compila separadamente y est asociada con un registro de activacin que puede ser reservado antes de la ejecucin, es decir que las variables pueden ser creadas antes de la ejecucin y sus vidas se extienden durante toda la ejecucin del programa (variables estticas). El mbito de una variable, sin embargo, est limitado a la unidad en la cual es declarada. Las unidades pueden acceder a variables globales declaradas va sentencias COMMON. Estas variables pueden ser vistas como pertenecientes a un registro de activacin provisto por el sistema, global a todas las unidades del programa. Un programa FORTRAN en el ambiente SIMPLESEM: ip
Segmento de cdigo para la unidad 1. Segmento de cdigo para la unidad 2 . . . . Segmento de cdigo para la unidad n Registro de activacin para datos globales Registro de activacin para la unidad 1 Registro de activacin para la unidad 2 . . Registro de activacin para la unidad n

Memoria de Cdigo

Memoria de Datos (D)

Figura 3.2. Un subprograma FORTRAN en el ambiente SIMPLESEM. Un programa FORTRAN debe pasar por un proceso de tres pasos de transformacin antes de tomar la forma de la figura anterior. En el primer paso (compilacin) cada unidad se Captulo 3: La estructura de los LP Pgina 18

Programming Language Concepts 2/E

Ghezzi-Jazayeri

traduce (por un compilador) separadamente sin conocimiento de las otras unidades. Como resultado de este paso, todas las sentencias son trasladadas a lenguaje de mquina de SIMPLESEM, pero todas las referencias a memoria (ya sea datos o cdigo) son trasladadas a un par (nombre de unidad, desplazamiento). En el segundo paso (el linkado) las unidades que componen el programa son linkadas (por un linkador). Como resultado, todas las unidades son asignadas a posiciones de memoria especficas y por lo tanto las referencias a memoria son trasladadas a direcciones de memoria especficas. El tercer paso (la carga) simplemente consiste en cargar el programa en la memoria de SIMPLESEM y hacer que ip apunte a la primera instruccin del programa. Este paso se realiza con un cargador. Describiremos estos pasos en detalle con el siguiente ejemplo. Para describir la semntica de cada sentencia FORTRAN detallaremos las acciones especficas que sta implica en nuestro procesador abstracto. Las sentencias semnticamente interesantes son aquellas que causan transferencia de control, especialmente entre unidades, porque ellas efectan un cambio en el ambiente. Revisaremos slo estas sentencias aqu, las otras sern encontradas en los ejercicios. La primer posicin de cada registro de activacin (desplazamiento 0) est reservada para un puntero de retorno. Para una instancia de la unidad A, el puntero de retorno contendr la direccin de la instruccin que deber ser ejecutada luego de terminar con la unidad A. La informacin restante en el RA es para las variables locales. (ms tarde, cuando discutamos el paso de parmetros, veremos que el registro de activacin tambin debe contener informacin acerca de los parmetros). Durante el proceso de traduccin de una unidad, se van reservando posiciones consecutivas del RA de la unidad para las variables locales a medida que se van encontrando las declaraciones de variables, es decir que los offsets son asociados estticamente a las variables. Cada referencia a una variable es as trasladada en un par (nombre de unidad, desplazamiento). Observe que si supieramos donde debera ser almacenado en la memoria cada registro de activacin, entonces podramos trasladar cada referencia a una variable a una direccin de memoria y no necesitariamos mantener el nombre de la unidad. En general, sin embargo, como el RA se asociar a una direccin particular en memoria recin en tiempo de carga, el traductor no puede asignar direcciones de memoria completas. Similarmente, cada instruccin tambin puede ser referenciada usando un par: nombre de unidad y desplazamiento de la instruccin dentro del segmento de cdigo. La habilidad para referenciar instrucciones ser necesaria para las estructuras de control como sentencias goto y llamadas a procedimientos. En tiempo de linkado, cuando todas las unidades de programa son combinadas por el linkador, el linkador puede comenzar asignando unidades a memoria una a la vez. El linkador ubica segmentos de cdigo en la memoria de cdigo y RA en la memoria de datos. Cuando cada segmento se asigna a memoria, cada par que se refiere a un RA o segmento de cdigo puede ser trasladado a una direccin de memoria especfica, es decir que si el RA de la unidad i se asigna a la memoria a partir de la posicin 1024, la posicin de datos referenciada por (i,6) podra ahora ser direccionada como D[1030] (ya que el desplazamiento 0 contiene el puntero de retorno). Para hacer la descripcin ms fcil de seguir, usaremos la notacin d[i,j] para referirnos a Captulo 3: La estructura de los LP Pgina 19

Programming Language Concepts 2/E

Ghezzi-Jazayeri

la posicin j del RA para la unidad i; c[i,j] para referirnos a la instruccin en el desplazamiento j del segmento de cdigo de la unidad i; &X, donde X es c[i,j] o d[i,j], para indicar la direccin de X. El linker trasladar estas cantidades a valores particulares (direcciones de memoria). Es decir que el traductor trasladar nombres de variables a un par c[i,j] o d[i,j], y el linkador los trasladar a C[m] o D[m]. Ud. debera una vez ms ver la Figura 3.2, la cual muestra una vista conceptual de un tpico programa FORTRAN en la memoria de nuestra mquina abstracta. Un programa se ejecuta cuando se carga en memoria (como se describi antes) y luego el puntero de instruccin se modifica para apuntar a la primera instruccin del programa. La semntica de una sentencia goto puede ser vista muy simplemente: reemplazar el contenido del puntero de instruccin con la direccin de destino de la sentencia goto. Observe que el traductor traslada la etiqueta en un par y el linkador traslada el par en una direccin real. En otras palabras: goto x significa: ip:= &c[i,j], donde i es la unidad que est siendo trasladada y x es la etiqueta de la instruccin localizada en el desplazamiento j del segmento de cdigo de la unidad i. Llamada y retorno a procedimientos son las construcciones mas interesantes semnticamente.Una llamada a un procedimiento P se traslada a la siguiente secuencia de instrucciones: 1. Almacenar la direccin de la prxima instruccin en d[P,0] (almacena el punto de retorno en la posicin 0 del RA de la unidad P). 2. Reemplazar el contenido del puntero de instruccin con &c[P,0], la primera instruccin de la unidad P (transfiere el control a la unidad P). : d[P,0] := ip + 2 { ip + 2 porque una llamada se traduce en dos instrucciones } ip:= &c[P,0] Despus del linkado estas instrucciones se convertirn en: D[m] := ip + 2 ip:= n { m es la direccin asignada al RA de P }

{ n es la direccin asignada al segmento de cdigo de P }

Un retorno de un procedimiento en la unidad P se traduce en: 1. Reemplazar el puntero de instruccin con el contenido de d[P,0], donde se almacena el punto de retorno de la unidad P. : ip := d[P,0] Captulo 3: La estructura de los LP Pgina 20

Programming Language Concepts 2/E

Ghezzi-Jazayeri

lo cual, luego de linkado, se convierte en: ip := D[m] { nuevamente, m es la direccin asignada al RA de P }

Para entender mejor el SIMPLESEM y nuestro enfoque de la semntica, veamos un ejemplo completo a travs de un pequeo programa FORTRAN, sus varias etapas de traduccin y su ejecucin mediante SIMPLESEM. Es importante entender este ejemplo completamente para que Ud. sea capaz de entender los lenguajes ms complicados que veremos en los prximos ejemplos. El programa (el cual no realiza ninguna tarea til) consiste del siguiente programa principal: INTEGER I,J COMMON I CALL X GOTO 10 CONTINUE END

10

y la siguiente subrutina: SUBROUTINE X INTEGER K,J COMMON I K=5 I=6 J=I+K RETURN END (CONTINUE es una sentencia FORTRAN que no tiene efecto. Se traduce como noop, de no operacin). El traductor realiza las siguientes asociaciones para el programa principal: I J 10 X d[COMMON,0] d[MAIN,1] d[MAIN,3] c[X,0]

y las siguientes asociaciones para la subrutina: I d[COMMON,0] K d[X,1] J d[X,2] El linkador realizar una asociacin ms exacta de direcciones combinando el programa principal y la subrutina X. Asignar memoria de datos al RA COMMON, RA para MAIN, y RA para X y asignar memoria de cdigo a MAIN y a X. Como resultado, producir las siguientes asociaciones en el programa principal: Captulo 3: La estructura de los LP Pgina 21

Programming Language Concepts 2/E

Ghezzi-Jazayeri

I D[0] J D[2] 10 C[3] X C[5] y las siguientes asociaciones para la subrutina X: I D[0] K D[4] J D[5] Observe como la variable I common se referencia por la misma direccin en MAIN y en X, mientras que las dos J (que son distintas) tienen diferentes direcciones asignadas. La figura 3.3 muestra este programa una vez cargado en memoria de SIMPLESEM. La figura 3.4 muestra pequeas vistas de la ejecucin del programa FORTRAN. Observe que los cambios ocurren en ip y la memoria de datos mientras que la memoria de cdigo permanece sin cambios a travs de la ejecucin del programa. Adems observe que en este ejemplo el contenido del punto de retorno para el programa principal no es usado, porque MAIN no retorna a un llamador. En una computadora real, MAIN es 'llamado' por el sistema operativo y esta posicin puede ser usada para retornar al sistema operativo. 3.6.2. La estructura de lenguajes tipo ALGOL. La mayora de los lenguajes discutidos en los captulos siguientes son descendientes del ALGOL 60 y a menudo son llamados lenguajes parecidos a ALGOL. La caracterstica ms distintiva de los lenguajes tipo ALGOL es la estructura de bloque, la cual es usada para controlar el mbito de las variables y para dividir el programa en unidades. Dos unidades cualquiera (o bloques) en el texto del programa, pueden ser disjuntas (no tienen porcin en comn) o anidadas (una unidad est completamente encerrada dentro de la otra). La estructura del programa puede ser vista como un anidamiento esttico de unidades (como se ve en la figura 3.5(a)). Tal anidamiento esttico puede tambin ser representado por una estructura de rbol. La estructura de rbol en la figura 3.5(b), para el programa de la figura 3.5.(a) muestra claramente que las unidades B y E estn estticamente encerradas dentro de A, las unidades F y G estn estticamente encerradas dentro de E, C dentro de B y D dentro de C. Una variable declarada en la unidad ms externa (en la raz del rbol de anidamiento), se dice que tiene nivel 0, las variables en las unidades encerradas por la unidad ms externa tienen nivel 1 y as sucesivamente. Usaremos la notacin nivel(u) para referirnos al nivel de anidamiento de la unidad u. Si una entidad tal como una variable est localmente declarada en una unidad U, es visible en U, pero no en las unidades que encierran a U. Sin embargo, es visible (con una excepcin) a todas las unidades que estn estticamente anidadas dentro de U. En la fig. 3.5 una variable declarada en la unidad B no es visible a A, E, F y G; es visible a B, C y D. Es local a B y global para C y D. Captulo 3: La estructura de los LP Pgina 22

Programming Language Concepts 2/E

Ghezzi-Jazayeri

La excepcin a la regla ocurre cuando una variable local a una unidad dada tiene el mismo nombre que una variable declarada en una unidad anidada. En tal caso, el mismo nombre denota al objeto declarado localmente y al objeto declarado globalmente en la unidad externa. La convencin en lenguajes del tipo ALGOL es que las declaraciones locales enmascaran a las declaraciones no locales. Esto significa que si en la figura 3.5, una variable v es declarada en A y en C, cualquier referencia a v en A, B, E, F, y G se refiere a la variable v local a A, mientras que cualquier referencia a v en C y D se refiere a la variable v local a C. En general, para determinar las variables visibles a una unidad, es necesario proceder desde aquella unidad hacia el exterior a travs de todos los niveles de anidamiento esttico. Cada declaracin de variable que no fue previamente encontrada define un nombre visible a la unidad. En lenguajes tipo ALGOL, las unidades caen en dos categoras: bloques y subprogramas. Los bloques son unidades sin nombre; se activan cuando se encuentran durante el progreso normal de la ejecucin y slo sirven para definir un nuevo ambiente por medio de declaraciones locales. Los subprogramas, por otro lado, son unidades con nombre activadas slo cuando ellas son llamadas explcitamente. Las reglas de mbito para los nombres de los subprogramas son las mismas que las descriptas para las variables. Por ejemplo, en la fig. 3.5, los nombres F y G son locales a la unidad E. Los nombres B y E son locales a la unidad A. Los nombres F y G no son visibles en las unidades B, C o D y por lo tanto B, C o D no pueden invocar a F o G directamente. Es interesante observar que el nombre B es local a A y por lo tanto puede ser usado dentro de A; tambin puede ser usado dentro del mismo B (recursivamente) pero su uso, es entonces como un un smbolo no local. Este punto es confuso para los principiantes pero es consistente con las reglas de mbito. Tres propiedades semnticas de los lenguajes tipo ALGOL, tornan inadecuado el esquema de reserva esttica de memoria que estudiamos en la seccin anterior. Una es que ellos permiten invocaciones recursivas de unidades, lo cual implica que el nmero de instancias de una unidad (el nmero de RA requeridos para la unidad en algn momento) no puede ser determinado en forma previa a la ejecucin del programa. En cada invocacin de la unidad un nuevo RA debe ser alojado con copias nuevas de las variables locales.La segunda razn que hace inadecuado la reserva esttica de memoria es que los lenguajes tipo ALGOL permiten declaraciones de la forma: array [m:n] of integer donde los valores de m y n son slo conocidos en tiempo de ejecucin cuando se activa la unidad. Esto implica que el traductor no puede calcular el tamao del RA para las unidades que contengan tales declaraciones.La tercer razn es que la mayora de estos lenguajes permiten al programador crear objetos de datos en puntos arbitrarios durante la ejecucin del programa (ALGOL 60 no permite esto). Para modelar el comportamiento en tiempo de ejecucin de los lenguajes tipo ALGOL, es necesario especificar las reglas que gobiernan el tiempo de vida de las variables y la creacin de ambientes de referencia para las instancias de las unidades. Generalmente, todas las variables locales de una unidad son automticamente creadas cuando se activa la Captulo 3: La estructura de los LP Pgina 23

Programming Language Concepts 2/E

Ghezzi-Jazayeri

unidad, es decir, que no hay una operacin "create" explcita. Adems, algunos lenguajes requieren que la cantidad de almacenamiento necesitada por cada variable sea fija y estticamente conocida (caso discutido en la seccin 3.6.2.1) En otros lenguajes, la cantidad de memoria necesitada para cada variable generalmente es conocida slo en tiempo de ejecucin cuando se activa la unidad (caso discutido en la seccin 3.6.2.2). En otros casos, las variables locales pueden ser explcitamente creadas por el programador, por lo tanto el monto de memoria requerido por un RA no se conoce cuando se activa la unidad, y crece dinmicamente cuando se ejecuta una nueva sentencia de creacin. Este caso se discute en 3.6.2.3. Un aspecto final importante (cmo las instancias de unidades tipo ALGOL acceden ambientes no locales) se discute en la seccin 3.6.2.4. En las siguientes secciones hicimos dos suposiciones: no hay compilacin separada y no hay referencias a smbolos no locales, lo cual nos permite saltear el linkado y traducir el programa a SIMPLESEM sin smbolos reubicables directamente en un slo paso. Adems, cada referencia a cdigo (etiquetas o procedimientos) puede ser trasladada directamente a direcciones absolutas en la memoria de cdigo, y las referencias a las variables pueden ser trasladadas a un nmero simple en lugar de un par: este nmero es su desplazamiento dentro del R.A. La suposicin subyacente es que la direccin completa ser evaluada por SIMPLESEM en tiempo de ejecucin, cmo lo veremos a continuacin. 3.6.2.1. Registros de activacin con tamao estticamente conocido. En este caso, las variables locales se crean implcitamente cuando la unidad se activa, y la cantidad de memoria requerida para mantener los valores de cada variable local se conoce en tiempo de traduccin. Pascal (si ignoramos los punteros) y C son dos lenguajes de esta clase. Como en el caso de FORTRAN, el tamao del R.A. y el desplazamiento de cada variable local dentro de l, son conocidos en tiempo de traduccin. Sin embargo, el tamao del R.A. no puede ser asociado al segmento de cdigo de la unidad estticamente (antes de la ejecucin) porque podra haber varias activaciones recursivas de la unidad al mismo tiempo. El R.A. por lo tanto debe ser alojado y asociado dinmicamente para cada nueva activacin. Consecuentemente, en tiempo de traduccin una variable puede ser ajustada slo a su desplazamiento dentro del R.A.; la asociacin del espacio fsico requiere conocimiento de la direccin del R.A., y esto slo puede ser hecho en tiempo de ejecucin. Las variables de sta clase se conocen como variables semiestticas. Por ejemplo, supongamos que la siguiente declaracin aparece en una unidad: a: array [0..10] of integer; (un arreglo de enteros con ndices en el rango 0 a 10). Tambin supongamos que cada entero ocupa una posicin en el R.A. y que para el arreglo se reservan celdas de memoria consecutivas. En tiempo de traduccin, el desplazamiento de a, offset(a), se conoce relativo a la direccin de la primer posicin del R.A., la cual ser conocida en tiempo de ejecucin. Si esta direccin es x, una referencia a a[i] se evala como una referencia a la posicin x+offset(a)+i. La reserva dinmica de los R.A. tiene dos consecuencias principales: permite la Captulo 3: La estructura de los LP Pgina 24

Programming Language Concepts 2/E

Ghezzi-Jazayeri

implementacin de activaciones recursivas de unidades y permite una utilizacin ms eficiente del almacenamiento de datos. Para hacer posible el retorno luego de una activacin, los RA deben contener suficiente informacin para identificar la instruccin a ser ejecutada y el RA que estar activo luego del retorno. Ya hemos visto el uso del puntero de retorno en el desplazamiento 0 en el caso del FORTRAN; ahora tambin reservaremos la posicin en el desplazamiento 1 de cada RA para un puntero al RA de la unidad llamadora (este puntero se llama el enlace dinmico). La cadena de enlaces dinmicos en el RA actualmente activo se llama cadena dinmica y representa la anidacin dinmica de las activaciones de las unidades. Cuando una unidad U completa su instancia, su RA ya no se necesita ms. Las reglas de tiempo de vida especifican que cada instancia de U deben tener un nuevo RA. Las variables locales de U pueden ser visibles slo a unidades que estn anidadas dentro de U y activadas luego de la activacin de U. Estas unidades terminan sus activaciones antes de que lo haga la instancia actual de U. Por lo tanto luego de que una unidad completa su instancia actual, es posible liberar el espacio ocupado por el RA y hacerlo disponible para almacenar nuevos RA. Por ejemplo, si A llama a B, la cual llama a C, los RA se alojan en el orden A, B, C. Cuando C retorna a B, el RA de C puede ser descartado; el RA de B, se puede descartar ms tarde cuando B retorna a A. Cmo el RA a ser liberado es el ltimo que fue alojado, los RA pueden ser alojados con una poltica LIFO. (PILA). Veamos la semntica operacional de las activaciones de las unidades en el caso de los lenguajes tipo ALGOL. Modificaremos SIMPLESEM levemente para permitirle manejar pilas de RA. Asumiremos que SIMPLESEM tiene dos punteros ms adems de ip. El primero se llama free y siempre apuntar a la prxima posicin disponible en la memoria de datos. El segundo se llama current y siempre apuntar al comienzo del RA actual (en la implementacin de un lenguaje en una mquina real, esta informacin generalmente se mantiene en los registros de la mquina). Cada vez que se accede a una posicin en la memoria de datos, SIMPLESEM automticamente suma el valor de current a su desplazamiento, es decir que usaremos d[i] para referirnos a D[current+i]. Podemos usar estos dos punteros para ilustrar el efecto de las activaciones de unidades. Por ejemplo, una llamada a un procedimiento 'p', es equivalente a: D[free]:=ip+5 { setea el punto de retorno; hay 5 instrucciones SIMPLESEM correspondientes a una llamada } D[free+1]:=current { setea el enlace dinmico correspondiente al nuevo RA } current:=free { setea current a la base del RA a ser alojado } free:=free+S { actualiza free; S es el tamao del RA para p } ip:= direccin de comiento del cdigo de segmento de p. El retorno de un procedimiento, o end (terminacin de una unidad), corresponde a: free:=current { remueve el registro del RA actual } current:=D[current+1] { resetea current usando el enlace dinmico } ip:=D[free] { usa el punto de retorno para setear IP } Por ejemplo, si las unidades F y G del programa mostrado en la figura 3.5 son subprogramas mutuamente recursivos, la figura 3.6. representa una descripcin parcial del Captulo 3: La estructura de los LP Pgina 25

Programming Language Concepts 2/E

Ghezzi-Jazayeri

estado de SIMPLESEM (slo se muestran las cadenas dinmicas, free y current) luego de la siguiente secuencia de llamadas: A llama a E E llama a F F llama a G G llama a F F llama a G G llama a F El manejo del almacenamiento mediante una pila para los lenguajes tipo ALGOL, es una eleccin de la implementacin y no est implicada estrictamente por la semntica del lenguaje. La semntica del lenguaje slo requiere que las variables locales sean asociadas a un nuevo RA con cada activacin, y conceptualmente, los objetos locales de activaciones previas pueden continuar existiendo por siempre. Sin embargo, gracias a las reglas de mbito del lenguaje, un RA es inaccesible tan pronto como la instancia termina. Esto es porque elegimos alojar los RA en una pila. La posibilidad de reusar el almacenamiento liberado por los RA es lo que hace (en principio) ms eficiente, en el uso de la memoria, a la reserva de almacenamiento dinmica comparada con la esttica. Observemos en este punto que este esquema basado en pila tambin puede ser usado para implementar FORTRAN. Aunque discutamos FORTRAN en trminos de reserva de memoria esttica, no es un requerimiento que haya que implementarla de una forma determinada. El esquema basado en pila tiene ms potencia de la necesaria. Es responsabilidad del implementador decidir qu tcnica emplear. De hecho, algunas implementaciones de FORTRAN usan un esquema basado en pila. Estas implementaciones a menudo como una extensin al FORTRAN estndar ya que no hay razn de la implementacin para deshabilitarla. 3.6.2.2. Registros de activacin cuyo tamao se conoce en la activacin de la unidad: En ALGOL 60 y otros lenguajes de la familia, como ALGOL 68, y ms recientemente Ada, el tamao del RA y la posicin de cada variable local dentro del RA, se conocen siempre estticamente. Las variables son automticamente creadas cuando se activa la unidad, pero su tamao puede depender de valores que son conocidos slo en tiempo de ejecucin cuando se activa la unidad. Tal es el caso de los arreglos dinmicos: arreglos cuyos lmites se conocen en tiempo de ejecucin. Las variables de esta clase se llaman variables semidinmicas. (El trmino variables dinmicas ser usado para denotar variables cuyo tamao cambia en una forma completamente dinmica). La complicacin que la introduccin de las variables semidinmicas agrega al caso discutido en la seccin 3.6.2.1. es que las variables locales no pueden ser asociadas a un desplazamiento constante dentro del RA en tiempo de traduccin; y el trabajo de asociacin queda retardado a tiempo de ejecucin. Por ejemplo: suponga que la siguiente declaracin de ALGOL 68 aparece dentro de la unidad U: [1:n] int a; [1:m] real b; Captulo 3: La estructura de los LP Pgina 26

Programming Language Concepts 2/E

Ghezzi-Jazayeri

(a es un arreglo de enteros con ndices en el rango 1..n, y b es un arreglo de reales con ndices en el rango 1..m). Sean m y n dos variables no locales. Como los valores de n y m no son conocidos en tiempo de traduccin, la cantidad de memoria necesitada para a y b, y por lo tanto para el RA de U, no puede ser determinada estticamente. Sin embargo, la semntica del lenguaje requiere que los valores de m y n (y por lo tanto el tamao del RA) sea conocido al momento de la activacin de la unidad. Esto permite una implementacin ms eficiente como se describe abajo. En tiempo de traduccin, podemos reservar, dentro del registro de activacin, almacenamiento para los descriptores de los arreglos dinmicos a y b. El descriptor incluye una celda con un puntero al rea de almacenamiento para el arreglo dinmico y una celda para los lmites inferior y superior de cada dimensin del arreglo. Debido a que el nmero de dimensiones del arreglo se conoce en tiempo de traduccin, el tamao del descriptor se conoce estticamente. Todos los accesos a variables semidinmicas se trasladan como referencias indirectas a travs del puntero en el descriptor, cuyo desplazamiento se determina estticamente. En tiempo de ejecucin, el RA se aloja en varias etapas. Primero, se aloja el almacenamiento requerido para las variables semiestticas y los descriptores de las variables semidinmicas. Cuando se encuentra la declaracin de una variable semidinmica, mediante las entradas de dimensin, se evala el tamao real de las variables semidinmicas y el RA se expande para inclur la variable. (Esta expansin es posible, porque siendo la unidad activa, el RA est en el tope del stack). Finalmente, el puntero en el descriptor se setea para apuntar al rea recin alojada. Mas especficamente, ahora necesitamos asociar algunas acciones en tiempo de ejecucin con declaraciones de variables semidinmicas. En el caso del array a previamente declarado mantendremos un descriptor para l en algn desplazamiento m. El descriptor contendr la direccin de comienzo de a, el lmite inferior, y el lmite superior en las posiciones m, m+1 y m+2 respectivamente. Al entrar a la unidad U, necesitamos tener algn cdigo para reservar espacio para a (incrementando free), y actualizar los contenidos del descriptor de a, ya que toda la informacin necesaria es ahora conocida. Cualquier acceso a elementos de a se traduce a referencias indirectas. Suponiendo que cada entero ocupa una posicin en la memoria de datos y que i se almacena en el desplazamiento s: a[i] significa D[D[current+m]+D[current+s]] donde la primer parte indica la direccin de comienzo de A y la segunda el desplazamiento de i. 3.6.2.3. Registros de activacin con tamao variable dinmicamente: Los lenguajes descriptos en las secciones previas estn caracterizados por tener todas las variables locales implcitamente creadas en tiempo de activacin de la unidad. Adems, el tamao de los RA se conoce estticamente, o en el peor de los casos, al momento de activar la unidad. Los lenguajes de la familia ALGOL (excepto ALGOL 60) desde PL/I y Pascal hasta ALGOL 68 y ADA, tambin permiten al programador tratar con objetos de datos cuyo tamao puede variar durante la ejecucin del programa. Consecuentemente, la Captulo 3: La estructura de los LP Pgina 27

Programming Language Concepts 2/E

Ghezzi-Jazayeri

cantidad de memoria requerida por un RA no se conoce cuando se activa la unidad. Tales variables se conocen como variables dinmicas. Un buen ejemplo de variables dinmicas es el arreglo flexible provisto por ALGOL 68 y otros lenguajes. Un arreglo flexible es un arreglo cuyos lmites pueden variar durante la ejecucin del programa para acomodar el tamao del objeto que se le desea asignar. Consideremos la siguiente estructura de programa tipo ALGOL: begin Unit A; begin Unit B; end B; end A; Sea x un arreglo flexible declarado localmente en la unidad A, y supongamos que a x se le asignan valores dentro de la unidad B. El almacenamiento para mantener el valor de x no puede ser reservado en el stack dentro del RA de A, porque la cantidad de memoria necesaria para mantener el valor asignado por la unidad B, se conoce slo cuando se ejecuta B (esto es cuando el RA de B est en el tope de la pila). En ese punto de la ejecucin, el RA de A est ms abajo en la pila, y cambiar su tamao para acomodar el objeto recientemente asignado a x podra requerir reestructurar el stack (algo no muy cmodo). Otro ejemplo de variables dinmicas lo proveen las variables que son alojadas bajo control del programa. Estas existen en PL/I, Pascal, y otros lenguajes, y permiten la creacin de estructuras de datos que pueden ser expandidas y contradas. Tales estructuras de datos pueden ser modeladas como formadas por un conjunto de nodos; los nodos pueden ser agregados y borrados de la estructura dinmicamente. Ejemplos de estas estructuras de datos son las listas enlazadas y los rboles. Los nodos estn a menudo conectados mediante punteros. Como los nodos son alojados cuando el programa se est ejecutando y su nmero no es conocido cuando se escribe el programa o en tiempo de traduccin, es imposible nombrarlos explcitamente. Por ejemplo, en Pascal, cada puntero est calificado, de tal forma que es capaz de apuntar nicamente a objetos de un slo tipo. Suponiendo que p ha sido declarado como un puntero de tipo t, la sentencia: new(p) crear un objeto de tipo t y asignar su direccin a p. El tiempo de vida de un objeto creado de esta forma, a diferencia de las variables semiestticas y semidinmicas, no termina cuando se termina el bloque que contiene la sentencia de alocacin. Algunos lenguajes (PL/I) contienen sentencias para desalojar tales objetos explcitamente. Otros lenguajes (Pascal) establecen que los objetos viven siempre y cuando una referencia a ellos (un puntero) exista. Adems es posible crear varios objetos sin desalojar ninguno de ellos. Es fcil ver, por lo tanto, que tales objetos no pueden ser alojados en el stack. Consideremos nuevamente, la estructura mencionada antes con una unidad A y una unidad B. Sea p un puntero declarado en la unidad A. La unidad B contiene la sentencia de alocacin "new(p)". Cuando sta sentencia se ejecuta, el RA que est en el tope de la pila es el de la unidad B. El objeto no puede ser alojado en el tope del stack, porque cuando termine B, el objeto tambin sera desalojado (aunque p todava apunta a l). Alojar el objeto en el RA de A tambin sera complicado por que como vimos en el caso Captulo 3: La estructura de los LP Pgina 28

Programming Language Concepts 2/E de los arreglos flexibles, requerira reestructurar la pila.

Ghezzi-Jazayeri

Resumiendo: las variables dinmicas significan objetos de datos cuyo tamao y/o nmero puede variar dinmicamente durante sus tiempos de vida. Este hecho prohbe la alocacin de estos objetos en el stack: ellos se alojan en un rea de memoria llamada heap. El trmino heap denota libertad de la connotacin de estrategia LIFO del stack). Por esta razn, las variables dinmicas son a menudo llamadas variables heap, contrariamente a las variables semiestticas y semidinmicas, las cuales son llamadas variables stack. En SIMPLESEM, como el stack de RA se aloja siempre en las direcciones ms bajas de memoria y crece hacia las direcciones ms altas, podramos alojar el heap en el otro extremo de la memoria, comenzando en la direccin ms alta y creciendo hacia las ms bajas. De hecho, esta es la forma en que estn diseadas la mayora de las implementaciones actuales. 3.6.2.4. Accediendo a entornos no locales: Hasta ahora, slo hemos examinado como la activacin de una unidad puede referenciar su propio entorno local. Ahora discutiremos como las activaciones de unidades pueden referirse a su entorno global; el problema del paso de parmetros ser discutido en la prxima seccin. En un lenguaje como FORTRAN, las variables globales pueden ser consideradas como pertenecientes a un RA suministrado por el sistema que es global para todas las unidades, y todas las asociaciones entre variables globales de una unidad y espacio de almacenamiento para tales variables globales puede ser establecida estticamente. En lenguajes tipo ALGOL, los RA que estn en el stack en un momento dado representan la cadena dinmica de activaciones de unidades. La figura 3.6 muestra el stack de nuestro procesador abstracto luego de la secuencia de llamadas descriptas en 3.6.2.1. para el programa mostrado en la fig. 3.5. Supongamos que la variable entera x est declarada en E y G, y que la variable entera y est declarada dentro de G, B y A. Adems, z es una variable entera declarada localmente dentro de F. Supongamos que la ejecucin de SIMPLESEM alcanza la asignacin z:=x+y cuando el stack de RA es el que se muestra en la fig. 3.6. Como ya hemos visto, la asociacin apropiada a una posicin del stack para z est parcialmente resuelta en tiempo de traduccin trasladando cada ocurrencia de z en una referencia a current+offset(z) (donde offset z es un valor estticamente conocido), y queda completamente resuelta en tiempo de ejecucin cuando el valor de current (la direccin de comienzo del RA de F) se hace conocido. Pero qu pasa con x e y? Observemos que el binding entre x ( y) y la posicin de la pila para l no es el binding ms recientemente establecido. En realidad, el binding ms recientemente establecido para la variable x ha sido establecido por la ltima activacin de la unidad G, pero las reglas de mbito del lenguaje requieren que la variable x referenciada dentro de F sea la misma declarada dentro de E. Similarmente, la variable y referenciada dentro de F es aquella declarada dentro de A, y no la alojada ms recientemente por la ltima activacin de G. En otras palabras: la secuencia de RA almacenados en el stack representa la secuencia de instancias de unidades dinmicamente generadas en tiempo de ejecucin. Pero lo que determina al mbito no local son las reglas Captulo 3: La estructura de los LP Pgina 29

Programming Language Concepts 2/E

Ghezzi-Jazayeri

de mbito del lenguaje, basadas en el anidamiento esttico de las declaraciones de unidades. 3.6.2.4.1. Cadena esttica: Una forma de hacer posible el acceso a las variables no locales es contener un puntero para cada registro de activacin (enlace esttico) al registro de activacin de la unidad que estticamente encierra a la unidad en el texto del programa. La fig. 3.7 muestra los enlaces estticas para la fig. 3.6. La secuencia de enlaces estticos que puede ser seguida desde el RA activo se llama cadena esttica. La referencia a las variables no locales puede explicarse intuitivamente como una bsqueda que atraviesa la cadena esttica. Para encontrar el binding correcto entre una variable y una posicin en el stack, buscamos a travs de la cadena esttica hasta que un binding sea encontrado. En nuestro ejemplo, una referencia a x se asocia a una posicin del stack dentro del RA de E, mientras que una referencia a y se asocia a una posicin del stack dentro del RA de A, como en verdad debe ser. En la prctica, la bsqueda, que impondra tiempo extra en el momento de la ejecucin, nunca es necesaria. Una solucin ms eficiente se basa en el hecho de que el RA que contiene una variable nombrada en una unidad U est siempre a una distancia fija desde el RA de U a travs de la cadena esttica. En trminos del rbol de anidamiento de la Fig. 3.5b., este atributo de distancia es el nmero de ramas de rbol que se necesitan atravesar para llegar desde la referencia a la variable, a la declaracin de la variable. Si la variable es local la distancia es obviamente 0 (por esto es que slo necesitbamos un offset en las secciones previas, porque asumamos slo variables locales); si es una variable declarada en la unidad inmediatamente anterior, la distancia es uno y asi sucesivamente. El atributo de distancia puede ser evaluado y asociado a la variable en tiempo de traduccin. Consecuentemente las variables pueden ser ajustadas a un par (distancia, offset) dentro del RA. Si la variable es semidinmica, el offset es la posicin relativa de un descriptor que contiene un puntero a una posicin del stack donde el objeto es almacenado. Si la variable es dinmica, el offset es la posicin relativa de un puntero al rea del heap donde el objeto es almacenado. El par (distancia, offset) se usa en tiempo de ejecucin como sigue. Si d es el valor de la distancia, comenzando desde la posicin current, hacemos d pasos a travs de la cadena esttica. El valor del offset es luego sumado a la direccin encontrada, y el resultado es la direccin real en tiempo de ejecucin del objeto de datos no local. En el caso de variables semidinmicas y dinmicas, este proceso nos lleva a una posicin en la cual se requiere un nivel extra de indireccin para alcanzar el objeto de datos. Podemos definir esto formalmente en trminos de una funcin recursiva fp(d), (de 'frame pointer') -un puntero a un RA- que est a d enlaces estticos a partir del RA activo. fp(d) puede ser definida como: fp(d)= if d=0 then current else D[fp(d-1)+2] Por ejemplo: fp(0) es simplemente current, y fp(1) es D[current+2]. La funcin fp puede ser traducida fcilmente en instrucciones SIMPLESEM. Dejamos esto al lector como Captulo 3: La estructura de los LP Pgina 30

Programming Language Concepts 2/E ejercitacin.

Ghezzi-Jazayeri

Usando fp, el acceso a una variable x, con el par (d,o) (distancia, offset), puede ser definido con la siguiente tabla: Tipo de variable Semiesttica Semidinmica Dinmica Semntica de Simplesem para el acceso a la variable D[fp(d)+o) D[D[fp(d)+o]] D[D[fp(d)+o]]

Tambin necesitamos modificar nuestra semntica de llamadas a procedimientos para instalar entradas de enlaces estticos en los RA. Esto puede ser hecho muy fcilmente. Consideremos que la unidad A llama a la unidad B. Como B es visible para A, debe estar en el mismo nivel de anidamiento, en un nivel de poca profundidad, o debe estar anidada directamente en A. Si a=level(A) y b=level(B), luego el enlace esttico a ser instalado para B debera setearse para apuntar al RA que est a-b+1 pasos a travs de la cadena esttica que se origina en A. Por ejemplo, si B est inmediatamente anidado dentro de A, luego la cadena esttica para B, se setea para apuntar al RA de A. Si A y B estn en el mismo nivel, la cadena esttica para B se setea para apuntar el RA de la unidad que inmediatamente encierra a A y a B. En otras palabras: un call B en la unidad A, significa: D[free]:= ip+6; {setea puntero de retorno} D[free+1]:=current; {setea enlace dinmico} D[free+2]:=fp(d); {donde d=level(A)-level(B)+1, setea enlace esttico} current:= free; {setea el puntero current actual} free:=free+tamao del RA de B {reserva lugar para el RA} ip:=direccin de comienzo de B en la memoria de cdigo {transfiere el control} Esta semntica no trabaja en la presencia de parmetros tipo procedimiento. Trataremos este caso en la Seccin 3.7. El retorno del procedimiento tiene exactamente la misma semntica que antes y no se necesita ningn tratamiento especial para los enlaces estticos. La principal desventaja del uso de los enlaces estticos es la necesidad de seguir la cadena esttica para cada referencia a una variable no local. El tiempo requerido para ubicar un objeto de datos no local no es constante y depende del nmero de pasos realizados en la cadena esttica. Hay otras tcnicas de implementacin que evitan este problema, pero no sern discutidas aqu, porque estamos interesados principalmente en la semntica, ms que en la eficiencia de la implementacin. 3.6.3. La estructura de los lenguajes dinmicos: El trmino 'lenguajes dinmicos' implica muchas cosas. En general, se refiere a aquellos lenguajes que adoptan reglas dinmicas en lugar de estticas. Por ej.: APL, LISP y SNOBOL4 usan reglas de tipado dinmico y de mbito dinmico. In principio, por Captulo 3: La estructura de los LP Pgina 31

Programming Language Concepts 2/E

Ghezzi-Jazayeri

supuesto, un diseador de lenguajes puede hacer cada una de estas elecciones independientes de las otras. Por ejemplo, uno puede tener reglas de tipado dinmico pero reglas de mbito estticas. En la prctica, generalmente, las propiedades dinmicas se adoptan juntas. En esta seccin, examinaremos como la adopcin de reglas dinmicas cambia la semntica de un lenguaje en trminos de requerimientos en tiempo de ejecucin. En general, una propiedad dinmica implica que mayor cantidad de bindings sern demorados a tiempo de ejecucin y no podrn ser hechos en tiempo de traduccin. Examinaremos el tipado dinmico y el mbito dinmico. En un lenguaje que usa tipado dinmico, el tipo de una variable y por lo tanto el mtodo de acceso o las operaciones permitidas no pueden ser determinadas en tiempo de traduccin. En las seccin 3.6.2. vimos que necesitamos mantener un descriptor en tiempo de ejecucin para las variables semidinmicas, porque no podramos en tiempo de traduccin, determinar el tamao o la direccin de comienzo de tales variables. En ese caso, el descriptor estaba para contener la informacin que no poda ser calculada en tiempo de traduccin (normalmente, la direccin de comienzo y los lmites del arreglo). Era posible mantener el descriptor en el RA porque el tamao del descriptor era fijo y conocido en tiempo de traduccin. En el caso de las variables tipadas dinmicamente, tambin necesitamos mantener el tipo de la variable en el descriptor. Si el tipo de una variable podra cambiar en tiempo de ejecucin, entonces el tamao y el contenido del descriptor tambin podran cambiar. Por ejemplo, si una variable cambia de un arreglo bidimensional a un arreglo tridimensional, entonces el descriptor necesita crecer para contener los valores de los lmites para la nueva dimensin. Esto contrasta con los descriptores de las variables semidinmicas cuyos contenidos eran fijos al momento de la activacin de la unidad (ver seccin 3.6.2). Cada acceso a una variable dinmica debe estar precedido por un chequeo en tiempo de ejecucin sobre el tipo de la variable seguido por el cmputo apropiado de la direccin, dependiendo del tipo actual de la variable. Entonces, que mantenemos para cada variable en el RA de una unidad? Como hemos visto no slo puede cambiar el tamao de la variable durante la ejecucin del programa sino tambien el tamao de su descriptor. Por lo tanto, no podemos mantener los descriptores en los RA (como hacamos con las variables semidinmicas). Para cada variable, mantendremos un puntero en el RA que apuntar al descriptor de la variable en el heap, el cual, a su vez, podra contener un puntero a la variable propiamente dicha en el heap. Enfoquemos ahora nuestra atencin hacia las reglas de mbito dinmicas. Usaremos APL como un ejemplo, aunque la discusin sirve para otros lenguajes con mbito dinmico como LISP y SNOBOL4. Un programa APL generalmente consiste de un nmero de subprogramas y una secuencia de sentencias, la cual puede ser vista como el programa principal (fig. 3.8). La primer lnea de la definicin de un subprograma declara los parmetros formales (I en el caso de SUB, N en el caso de FUN). Los subprogramas funciones tambin indican el parmetro resultado (R en el caso de FUN). Las variables locales (X en el caso de FUN, Y en el caso de SUB) tambin son declaradas. Los subprogramas pueden no estar anidados textualmente. Captulo 3: La estructura de los LP Pgina 32

Programming Language Concepts 2/E

Ghezzi-Jazayeri

Las variables globales son implcitamente declaradas por una asignacin a un identificador que no ha sido declarado como local. Una declaracin de una variable no especifica el tipo de la variable. Los nombres de subprogramas son considerados identificadores globales. Las reglas de mbito de APL son dinmicas, es decir que, el mbito de un nombre es totalmente dependiente de la cadena de llamadas en tiempo de ejecucin (la cadena dinmica), en lugar de la estructura esttica del programa. En el ejemplo mostrado en la fig. 3.8, consideremos el momento cuando se ejecuta la llamada a SUB2. Las referencias no locales a X y Z dentro de la activacin de SUB son asociadas a las X y Z globales definidas por el programa principal. Cuando la funcin FUN es activada desde SUB, la referencia no local a Y se asocia a la definicin ms reciente de Y, es decir, al objeto de datos asociado a Y en el RA de SUB. La prxima instruccin del programa principal llama a FUN nuevamente, en este caso, la referencia no local a Y en FUN se asocia a la Y global definida en el programa principal. La implementacin del mecanismo de referenciado global de APL puede ser muy simple. Los RA son alojados en el stack y encadenados por medio de enlaces dinmicos. Cada entrada del RA explcitamente registra el nombre de la variable y contiene un puntero al rea heap, donde puede almacenarse el valor. La alocacin en el heap es necesaria porque la cantidad de memoria requerida para cada variable puede variar dinmicamente (ver seccin 3.3.1). Para cada variable, por ejemplo T, se busca en el stack siguiendo la cadena dinmica. La primera asociacin encontrada para T en un RA es la correcta. La fig. 3.9 ilustra el stack para el programa de la fig. 3.8 cuando FUN es llamada por SUB, la cual a su vez, es llamada por main. Aunque simple, este mecanismo de acceso es ineficiente. Otro enfoque es mantener una tabla de las referencias no locales actualmente activas. En lugar de buscar a travs de la cadena dinmica, es suficiente una simple bsqueda en esta tabla. Esta solucin (no la discutiremos aqu) acelera la bsqueda de variables no locales a costa de acciones ms elaboradas que tienen que ejecutarse a la entrada y salida de los subprogramas. Estas acciones son necesarias para actualizar la tabla de referencias no locales activas. 3.7. Paso de Parmetros: El paso de parmetros permite la comunicacin de datos entre unidades de programas. A diferencia de la comunicacin va mbitos globales, los parmetros permiten la transferencia de diferentes datos en cada llamada y tienen ventajas en trminos de legibilidad y modificabilidad. La mayora de los LP usan un mtodo posicional para asociar los parmetros actuales a los formales en las llamadas a los subprogramas. Si el procedimiento est declarado como: subprogram S (F1,F2,....,Fn); . . . end S; Captulo 3: La estructura de los LP Pgina 33

Programming Language Concepts 2/E y la llamada al subprograma es: call S (A1,A2,......,An);

Ghezzi-Jazayeri

el mtodo posicional implica que los parmetros formales Fi se asociarn a los parmetros actuales Ai, i=1,2,...,n. Podemos dividir las entidades de programa que pueden ser pasadas como parmetros en tres clases: datos, subprogramas y tipos. Pasar un tipo a un procedimiento genrico involucra los conceptos descriptos en la seccion 4.7.3 para tipos de datos abstractos genricos. Estudiaremos parmetros de datos en la prxima seccin y parmetros de subprogramas en la Seccin 3.7.2. 3.7.1. Parmetros datos: Hay diferentes convenciones para el paso de parmetros datos a subprogramas. Es importante saber que convenciones son adoptadas por un lenguaje porque la eleccin afecta la semntica del lenguaje. El mismo programa puede producir diferentes resultados bajo diferentes convenciones de paso de parmetros datos. Tres convenciones para paso de parmetros son descriptas en las secciones 3.7.1.1 a 3.7.1.3. En trminos de SIMPLESEM, debemos reservar espacio en el R.A. para parmetros y variables locales. 3.7.1.1 Llamada por Referencia (o por Comparticin): La unidad llamadora pasa a la unidad llamada la direccin del parmetro actual (el cual est en el ambiente de la unidad llamadora). Una referencia al parmetro formal correspondiente en la unidad llamada se trata como una referencia a la posicin cuya direccin se pas. Una variable que se trasmite como un parmetro actual, es por lo tanto compartida y directamente modificable por el subprograma. Si un parmetro actual no es una variable, por ejemplo, una expresin o una constante, el subprograma recibe la direccin dentro del RA de la unidad llamadora de una posicin temporal que contiene el valor del parmetro actual. Algunos lenguajes tratan esta situacin como un error. 3.7.1.2. Llamada por copia En la llamada por copia -a diferencia de la llamada por referencia- los parmetros formales no comparten almacenamiento con los parmetros actuales; sino que actan como variables locales. As, la llamada por copia protege a la unidad llamadora de modificaciones inadvertidas de los parmetros actuales. Es posible adems clasificar la llamada por copia en tres submodos de acuerdo a la forma en que las variables locales que corresponden a los parmetros formales son inicializadas y a la forma en que sus valores afectan a los parmetros formales a la salida del procedimiento. Estos tres modos son: llamada por valor, llamada por resultado y llamada por valor-resultado. En la llamada por valor, la unidad llamadora evala los parmetros actuales, y estos Captulo 3: La estructura de los LP Pgina 34

Programming Language Concepts 2/E

Ghezzi-Jazayeri

valores se usan para inicializar los parmetros formales, los cuales actan como variables locales en la unidad llamada. La llamada por valor no permite ningn flujo de informacin de retorno al llamador, as que las asignaciones a los parmetros formales no afectan a la unidad llamadora. En la llamada por resultado, las variables locales correspondientes a los parmetros formales no son seteadas en la llamada al procedimiento, pero su valor, al retorno, se copia en la posicin del parmetro actual en el ambiente del llamador. La llamada por resultado no permite ningn flujo de informacin a la unidad llamada. En la llamada por valor-resultado, las variables locales que denotan parmetros formales son inicializadas en la llamada al subprograma (como en la llamada por valor) y al retorno, copian sus valores a los parmetros (como en la llamada por resultado). La llamada por valor-resultado y la llamada por referencia pueden tener efectos diferentes. 3.7.1.3. Llamada por nombre: Como en la llamada por referencia, un parmetro formal, mas que ser una variable local del subprograma, denota un posicin en al ambiente del llamador. A diferencia de la llamada por referencia, sin embargo, el parmetro formal no se asocia a la posicin en el momento de la llamada; sino que se asocia a una posicin (posiblemente diferente) cada vez que es usado dentro del subprograma. Consecuentemente, cada asignacin a un parmetro formal puede referirse a una posicin diferente. Bsicamente, en la llamada por nombre cada ocurrencia del parmetro formal se reemplaza textualmente por el parmetro actual. Esta regla aparentemente simple puede llevar a complicaciones insospechadas. Por ejemplo, el siguiente procedimiento, diseado para intercambiar los valores de a y b (a y b son parmetros por nombre). procedure swap (a,b: integer); var temp: integer; begin temp:=a; a:=b; b:=temp nd swap; puede producir resultados incorrectos e inesperados cuando es invocado por la llamada: swap (i, a[i]); La regla de sustitucin especifica que las sentencias a ser ejecutadas son: temp:=i; i:=a[i]; a[i]:=temp; Si i=3 y a[3]=4 antes de la llamada, luego de la llamada i=4, a[4]=3 y a[3] queda sin modificar!. Captulo 3: La estructura de los LP Pgina 35

Programming Language Concepts 2/E

Ghezzi-Jazayeri

Otra trampa es que el parmetro actual que es (conceptualmente) sustituido en el texto de la unidad llamada pertenece al ambiente de referencia del llamador, no a aquel de la activacin de la unidad llamada. Por ejemplo, supongamos que el procedimiento swap tambien cuenta el nmero de veces que es llamada y est includo en el siguiente fragmento: procedure x..... var c:integer; ............... procedure swap (a,b: integer); var temp: integer; begin temp:=a; a:=b; b:=temp; c:=c+1 end swap procedure y..... var c, d:integer; .......... swap(c, d); .......... end y ................ end x; Cuando el procedimiento y llama al procedimiento swap, la regla de sustitucin especifica que las sentencias a ser ejecutadas son: temp:= c; c:= d; d:=temp; c:=c+1; Sin embargo, la posicin asociada al nombre c en la ltima sentencia pertenece al registro de activacin del procedimiento x, mientras que la posicin asociada a las ocurrencias previas de c pertenecen al registro de activacin de y. Observe que el problema no es la identificacin del parmetro actual -lo cual puede hacerse fcilmente en tiempo de traduccin. El problema es la dificultad encontrada por el programador en prever las asociaciones en tiempo de ejecucin entre los parmetros actuales y formales. La llamada por nombre, por lo tanto, puede fcilmente favorecer la produccin de programas que sean difciles de leer. Adems es difcil de implementar. La tcnica de implementacin bsica consiste en reemplazar cada referencia a un parmetro formal con una llamada a un subprograma (llamado thunk) que evala una referencia al parmetro actual en el ambiente apropiado. El subprograma thunk se crea para cada parmetro actual. Obviamente, el peso de las llamadas en tiempo de ejecucin a los thunks hace demasiado costosa a la llamada por nombre. Captulo 3: La estructura de los LP Pgina 36

Programming Language Concepts 2/E

Ghezzi-Jazayeri

La llamada por referencia es el modo estndar de paso de parmetros de FORTRAN. La llamada por nombre es el estndar en ALGOL 60, pero opcionalmente, el programador puede especificar llamada por valor. SIMULA 67 provee llamada por valor, por referencia y por nombre. Pascal permite al programador pasar parmetros por valor o por referencia. Por ejemplo, la siguiente cabecera de un procedimiento: procedure x (y: integer; var z: nuevotipo) especifica que y se pasa por valor, mientras que z (del tipo nuevotipo) se pasa por referencia (lo cual es indicado por la palabra var). 3.7.2. Parmetros subprogramas. Muchos lenguajes permiten que los procedimientos sean pasados como parmetros. Esta facilidad es til en muchas situaciones prcticas. Por ejemplo, un subprograma S que evala propiedades analticas de funciones de una variable simple en un intervalo dado a..b puede escribirse sin conocer la funcin y puede usarse para diferentes funciones, si los valores de la funcin son producidos por un subprograma que se enva a S como parmetro. Otro ejemplo: si el lenguaje no provee caractersticas explcitas para el manejo de excepciones (ver captulo 5), uno puede transmitir el manejador de excepciones como un parmetro subprograma a la unidad que podra elevar la excepcin. Los parmetros subprogramas se comportan muy diferentemente en lenguajes con ambientes estticos y lenguajes con ambientes dinmicos debido a las diferentes formas en que estos lenguajes determinen el ambiente de referencia para una unidad. Consideraremos cada caso en las dos secciones siguientes. 3.7.2.1. Parmetros procedimientos en lenguajes con ambiente esttico. En esta seccin discutiremos los requerimientos semnticos de los parmetros procedimientos e lenguajes de ambiente esttico como el Pascal, C y FORTRAN, todos los cuales soportan esta caracterstica. Consideremos el programa de la figura 3.10. En este programa, b es llamado en el procedimiento main (lnea 14) con el parmetro actual a; dentro de b, se llama al parmetro formal x (lnea 11), el cual en este caso corresponde a a. Cuando a es llamado, este debera ejecutarse cmo si hubiera sido llamado directamente, es decir que no debera haber diferencias observables en el comportamiento de un procedimiento llamado directamente o a travs de un parmetro formal. En particular, la invocacin de a debe ser capaz de acceder el ambiente no local de a ( en este caso las variables u y v de main. Observe que estas variables no son visibles en b porque ellas estn enmascaradas por las variables locales de b con el mismo nombre). Esto introduce una leve dificultad para nuestro esquema actual. Cmo ud. puede recordar (seccin 3.6.2.4) la llamada a un procedimiento se traslada a varias instrucciones, una de las cuales debe setear el enlace esttico para el procedimiento llamado. En el caso de call x en b, esto es imposible en tiempo de traduccin porque no sabemos cual es el procedimiento x. Esta informacin en general slo ser conocida en tiempo de ejecucin. Podemos manejar esta situacin pasando el enlace esttico necesitado en el momento de la llamada. Captulo 3: La estructura de los LP Pgina 37

Programming Language Concepts 2/E

Ghezzi-Jazayeri

1 procedure main...... 2 var u,v : integer; 3 procedure a .... 4 var y : integer; .............. 5 end a; 6 procedure b ( procedure x); 7 var u,v,y : integer; 8 procedure c....... .................... 9 y:=....... .................... 10 end c; 11 x; 12 b(c); ................. 13 end b .............. 14 b(a); ................. 15 end main

Cmo sabemos que enlace esttico pasar? De las reglas de ambiente, sabemos que para que una unidad (en este caso, main) pueda pasar un procedimiento a a un procedimiento b debe: 1. Tener al

Figura 3.10: Programa con parmetros procedimientos. procedimiento a dentro de su ambiente, es decir que a debe ser visible no localmente o local (inmediatamente anidado); o 2. a debe ser una parmetro formal en main, es decir, algn procedimiento actual fue pasado a main como un parmetro procedimiento. Los dos casos pueden ser manejados de la siguiente forma: Caso 1: El enlace esttico a ser pasado es un puntero al RA que est a d pasos a travs de la cadena esttica originada en la unidad llamadora, donde d= nivel(main) -nivel(a) -1; la misma definicin utilizada en 3.6.2.4.1. Caso 2: El enlace esttico a ser pasado es aquel que fue pasado a main para a. Dejamos la tarea de formular estas reglas en trminos de SIMPLESEM como un ejercicio para el lector. Qu pasa con la llamada a un parmetro procedimiento? La nica diferencia de llamar a un procedimiento directamente est en la forma en que debe setearse el enlace esttico. El valor del enlace esttico es simplemente copiado desde el rea de parmetros. El programa en la figura 3.9 muestra otro punto sutil: cuando en un programa se usan parmetros procedimientos, las variables no locales visibles en un momento dado no son necesariamente aquellas del ltimo RA alojado por la unidad donde tales variables se declaran localmente. Por ejemplo, luego de la llamada recursiva a b cuando se pasa c (lnea 12), la llamada a x en b (lnea 11) invocar a c recursivamente. Entonces la Captulo 3: La estructura de los LP Pgina 38

Programming Language Concepts 2/E

Ghezzi-Jazayeri

asignacin a y en c (lnea 9) no modificar la y del ltimo RA para b sino aquella y alojada previamente a la ltima. La figura 3.11 muestra este punto. Repasemos el impacto de los parmetros procedimientos. Primero, nuestra descripcin semntica es ms complicada porque ahora el mecanismo bsico se ha extendido. Las llamadas a procedimientos, en particular, son considerablemente ms complicadas porque ellas tienen que tratar con diferentes tipos de objetos. La descripcin semntica de la llamada y su implementacin crecen en complejidad. En contraste con esto, podemos decir que agregar un nuevo operador aritmtico a un lenguaje no requiere casi ningn cambio a nuestra descripcin semntica. Podemos decir que la habilidad para pasar procedimientos como parmetros se agrega a la potencia semntica (y complejidad) de un lenguaje. 3.7.2.2. Parmetros procedimientos en lenguajes con ambiente esttico. Los parmetros procedimientos pueden causar un peculiar problema en lenguajes dinmicos, tales como LISP. Si consideramos el programa de la figura 3.10 bajo reglas de mbito dinmicas, cuando el procedimiento a se llama a travs de x, las referencias a u y v en a sern asociadas a u y v en b y no a aquellas en main. Esto es ciertamente dificultoso de usar y confuso, ya que cuando el procedimiento a fue escrito, era razonablemente correcto esperar acceder a u y v en main pero como b contiene variables con los mismos nombres, estas enmascaran las variables que se queran usar. Bien definido, el problema es que el ambiente no local, y por lo tanto el comportamiento del procedimiento, son dependientes de la secuencia dinmicas de llamadas que han sido hechas. Consideremos varios programadores trabajando en diferentes partes del mismo programa. Una desicion aparentemente inocua, como puede ser el nombre de una variable, puede cambiar el comportamiento del programa completamente. Como LISP es un lenguaje con mbito dinmico, tiene este problema. El problema, sin embargo, fue descubierto muy temprano en el desarrollo del LISP y la solucin aconsejada es preceder la definicin de una funcion con la palabra FUNCTION antes de pasarla como parmetro. Los procedimientos pasados de esta forma son pasados con sus ambientes no locales en el momento de la llamada, por lo tanto ellos siguen su ambiente esttico. Por supuesto, otra solucin diferente al problema habra sido adoptar reglas de mbito estticas para el lenguaje completo. Varios dialectos de LISP han sido desarrollados recientemente para respetar el ambiente esttico. El lenguaje Common LISP que est emergiendo como el estndar de LISP ha adoptado reglas de mbito estticas pero permite reglas de mbito dinmicas si lo desea el programador. 3.8. Definiciones oficiales de lenguajes. Comenzamos este captulo discutiendo mtodos precisos para definir lenguajes. Luego discutimos las diferentes propiedades semnticas de los lenguajes e intentamos dar definiciones precisas para ellas. Ahora enfocaremos nuestra atencin a una cuestin ms prctica como ser las definiciones de los lenguajes y las implementaciones de los Captulo 3: La estructura de los LP Pgina 39

Programming Language Concepts 2/E lenguajes.

Ghezzi-Jazayeri

Primero, la definicin de un lenguajes define los requerimientos que cada implementacin debe satisfacer. Una definicin de un lenguaje tambien especifica un nmero de cuestiones que deben ser determinadas por la implementacin. Por ejemplo, la definicin de Pascal establece que cada implementacin debe soportar un tipo llamado integer, pero no establece los valores mximo y mnimo para los integers; queda bajo responsabilidad de la implementacion definir estos valores. As, si ud. conoce un lenguaje, ud. debera ser capaz de usar sus diferentes implementaciones, pero algunas veces ser necesario referirse a los manuales de una implementacin particular para solucionar algunos temas que dependen de la implementacin. Pero, en general, las cuestiones de la implementacin tales como si los RA se alojan en un heap o un stack son irrelevantes para el usuario porque no afectan la semntica del lenguaje. Idealmente, para usar un lenguaje, ud. debera necesitar solamente estudiar la definicn del lenguaje y consultar el manual de la implementacin para los parmetros definidos por la implementacin. En la prctica, sin embargo, la mayora de las definiciones de los lenguajes son algo imprecisas e incompletas. Esto causa que el implementador haga ciertas suposiciones sobre la intencin de la definicin del lenguaje. Debido a que diferentes implementadores hacen diferentes suposiciones, las diferentes implementaciones diferirn basadas en estos aspectos. Estas diferencias son problemticas porque a menudo no son vistas como caractersticas dependientes de la implementacin el implementador a menudo est seguro de que la definicin es clara acerca de una cuestin particular. El libro de Zhan (Zhan, 1979) es una coleccin de tales diferencias en varias implementaciones de C. Como ejemplo, consideremos el siguiente fragmento de un programa C legal: 1. 2. 3. 4. 5. 6. 7. 8. case (i) { switch 1: a= b-a; break; while (a>d) { switch 2: b=c-d; } switch 3: c=a-b; }

La sentencia while en la lnea 4 tiene como cuerpo a la lnea 5. La lnea 5 parece ser una de las alternativas de la sentencia case que comienza en la lnea 1. Puede parte de una sentencia case estar includa dentro de una sentencia while? La definicin del lenguaje no explicita ninguna regla acerca de esta cuestin. Por lo tanto, algunas implementaciones lo permiten y otras no porque lo consideran ilegal. El problema con las diferencias en implementaciones del mismo lenguaje es que ellas inhiben la portabilidad de los programas. La nica forma de evitar este problema es tener definiciones precisas del lenguaje y asegurar que todas las implementaciones sigan estas definiciones. Esto a menudo no es prctico, si consideramos la realidad de como se crean las definiciones de los lenguajes. Generalmente el diseador publica la definicin o hace disponible una implementacin. Una vez que el lenguaje logra captar una masa aceptable de usuarios, esta implementacin se convierte en un candidato para la estandarizacin. Captulo 3: La estructura de los LP Pgina 40

Programming Language Concepts 2/E

Ghezzi-Jazayeri

Hay varias organizaciones dedicadas a producir estndares. Por ejemplo, el Instituto Nacional Norteamericano de Estndares (ANSI) ha producido estndares para varias reas de procesamiento de informacin tales como comunicacin de datos, y estndares para FORTRAN, COBOL, y Ada. Actualmente hay esfuerzos para producir estndares para Pascal y C. Otras organizaciones que producen estndares de lenguajes son la ISO y la IEEE. En el pasado, el estndar de un lenguaje era producido luego de que el lenguaje llevaba un tiempo de existencia y las consecuencias de la falta de un estndar eran severas. Para evitar estos problemas completamente, un estndar de Ada fue producido antes de que el lenguaje se haga de uso popular. Hay varias ventajas y desventajas de la estandarizacin temprana. Por ejemplo, al momento en que se lleg a un acuerdo para producir el ISO estndar para el Pascal, ya se haban detectado, mediante su uso, un gran nmero de problemas del Pascal original. Este conocimiento permiti al comit producir un estndar que estaba libre de estos problemas. Si el estndar se hubiese redactado antes, estos problemas habran formado parte del estndar y cada implementador los hubiera implementado. Como los estndares son lentos (y algunas veces imposibles) de cambiar, estos problemas se convierten en caractersticas permanentes del lenguaje. Por otra parte, la desventaja de producir un estndar para un lenguaje que ha estado en uso muchos aos es que el momento del desarrollo del estndar, existen muchas implementaciones del lenguaje y cada una de estas implementaciones extiende e interpreta las reglas del lenguaje a su manera. Es casi imposible (y no inteligente) desarrollar un estandar que trate todas estas implementaciones. Por lo tanto, el principal objetivo de un estndar -tener implementaciones compatibles en diferentes mquinas- puede ser imposible de lograr!. La evolucin del lenguaje Ada ha seguido un curso muy diferente a la de otros lenguajes. Ada fue definido y estandarizado antes de que existieran implementaciones real y antes de que su uso se hiciera extensivo. El objetivo del grupo Ada fue asegurar que todas las implementaciones tuvieran una posibilidad de conformar el estandar y que las diferencias tradicionales entre las implementaciones no existieran para Ada. Ellos trataron de descubrir la mayora de los problemas del lenguaje -lo cual generalmente se detecta a travs del uso del lenguaje- mediante extensivas revisiones del lenguaje. Adems se estableci un centro de validacin cuyo objetivo fue certificar que los compiladores que decan compilar Ada, realmente lo hacan. Todava queda por ver si el grupo Ada tuvo xito. Claramente, si es posible detectar la mayora de los problemas de un lenguaje, sin utilizar el lenguaje, es mejor estandarizar el lenguaje tan pronto como sea posible. La inevitabilidad de las estandarizaciones de los lenguajes ha llegado a los lenguajes LISP, el cual es conocido por la gran cantidad de diferentes implementaciones. Se establecieron dos ramas principales de LISP -MACLISP y INTERLISP-, cada una con muchos subdialectos. En 1981, varios representantes de los dialectos de MACLISP se reunieron y decidieron definir un lenguaje llamado Common LISP, que contiene las mejores caractersticas del LISP. La comunidad de INTERLISP se sum al esfuerzo. Se espera que todas las variantes del LISP trabajen bajo este lenguaje comn, pero an as, todos son libres de hacer extensiones al Common LISP. Las definiciones de lenguajes estndares se escriben muy cuidadoda y precisamente. Sin embargo el uso de los lenguajes naturales en la definicin de los documentos introduce la Captulo 3: La estructura de los LP Pgina 41

Programming Language Concepts 2/E

Ghezzi-Jazayeri

posibilidad de inconsistencias y ambiguedades que son muy difciles de detectar. En el captulo 9 discutiremos el papel que las definiciones formales de los lenguajes pueden jugar para aliviar este problema.

Captulo 3: La estructura de los LP

Pgina 42

S-ar putea să vă placă și