Está en la página 1de 54

Lenguaje de Programacion: Es una notación formal para describir algoritmos que se pueden ejecutar

una computadora. Tiene tres componentes: PRAGMÁTICA, SINTAXIS y SEMÁNTICA.

Pragmática: Son los problemas de construcción, todo lo hace al entorno, implementación, velocidad,
espacio, optimización. Es el estudio de los lenguajes que se ocupan de los problemas prácticos.
 Imperativo o Estructurado: C – Pascal – Basic – Cobol
 Orientado a Objetos: smalltack
 Funcional: Miranda
 Lógico: Prolog

Sintaxis: Estudia “como se escribe”. Es decir, definen como las sentencias pueden formarse con una
secuencia de símbolos. Utilizando estas reglas, puede determinarse si una sentencia es válida o no.
Ejemplo 1: en el lenguaje las variables empiezan con una letra y pueden seguir con letras y números.
Ejemplo 2: los verbos están entre el sujeto y el predicado. Ejemplo 3: cómo se escribe una iteración.

Semántica: Estudia “que significa”. Es decir, definen el significado de la sentencia y no de cómo se


escribe. Por ejemplo: En Pascal la declaración: var s: set of (red, while, blue) hace que se reserve espacio
para una variable llamada “s”. El valor que esta variable puede contener, esta limitado a los valores red,
white y blue.

Sintaxis

 Reglas Léxicas: El conjunto de caracteres que constituyen el alfabeto del lenguaje y la manera de
cómo se pueden combinar los caracteres para formar símbolos (tokens) válidos. Ejemplo 1:
controlan la ortografía. Ejemplo 2: PASCAL considera el uso de minúsculas y mayúsculas de manera
indistinta. En cambio C, las considera distintas.
 Reglas Gramaticales o sintácticas: Como se escribe una sentencia, un programa o una parte del
mismo.

Alfabeto Elementos del Sentencias o


lenguaje programas
Reglas léxicas
Reglas sintácticas
Alfabeto: conjunto de caracteres que reconoce un lenguaje de programación.
Elementos del lenguaje: secuencia de los símbolos del alfabeto que tienen un significado sencillo.
Ejemplos: palabra reservada, variables, etc.
Sentencias o programas: métodos, procedimientos, funciones.
¿Cuales son los símbolos terminales de las reglas sintacticas y como se generan? Los terminales
son los elementos del lenguaje, estos se generan por las reglas léxicas.

BNF: Es un metalenguaje. Que nos permite tener una definición compacta y clara de la sintaxis de un
lenguaje. Nace con Algol 60. Metalenguaje es un lenguaje que es usado para describir otros lenguajes.
 No Terminales: Las “cosas que se definen” <exp><term>. Son entidades linguísticas compuestas
por un conjunto de símbolos, determinodos por las las relgas de sintaxis. Los no terminales se
encuentran del lado izquierdo y se definen del lado derecho. El símbolo de comienzo es un no
terminal.
 Terminales: Las “cosas que no se definen” (números, letras). Deben ser símbolos válidos que
reconoce el lenguaje.
Limitaciones del BNF: No se pueden representar lenguajes naturales. No se puede expresar cotas
máximas y mínimas para números o longitudes. Ejemplo: Si quisiera definir que las constantes esten estre
2.256.256 y –2.256.256, tendría que generar una cantidad excesiva de reglas .
Conjunto de no terminales de las reglas léxicas: Son las constantes, palabras reservadas, variables,
etc. Estos son no terminales ya que se los puede expresar o definir en base al alfabeto del programa que
serian los elementos terminales.

Semántica

-1-
Binding (ligadura o atadura): Es el momento preciso en que se conoce una propiedad (tipo, valor,
alcance, almacenamiento) de un elemento (variable, constante, función, parámetro) de un lenguaje.
La información del binding se mantine en el descriptor.
 Estático: Cuando no cambia durante la ejecución. Conozco la propiedad antes de ejecutar el
programa. Ejemplo: int x (va a ser una variable entera durante toda la ejecución).
 Dinámico: La propiedad se conoce en tiempo de ejecución y puede ser cambiado, de acuerdo a las
reglas del lenguaje.
Variables
Caractristicas de las variables: Son el nombre, tiempo de vida, valor, tipo alcance, almacenamiento.
Binding de variables Estático Dinámico
Valor Constantes simbólicas Casi todas las variables
Tipo Mayoría (FORTRAN, ALGOL 60, COBOL, APL, LISP y SNOBOL4.
Pascal, ALGOL 68, SIMULA 67, CLU y Ada)
Alcance Mayoría (Fortran y Cobol) BASIC, APL, LISP y SNOBOL4.
Almacenamiento Fortran, Cobol Mayoría
Nombre: El nombre es usado para identificar y referirse a las variables. Algunos lenguajes permiten que
las variables no tengan nombres, llamadas variables anónimas. . Los punteros son el medio principal para
acceder a variables anónimas.

Tiempo de vida: 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.

Valor: El valor se representa codificado. El código de representación se interpreta de acuerdo al tipo de la


variable. El valor puede ser accedido por referencia (puntero) o por un path.
¿Cuál es el valor inicial de una variable? La mayoría de los lenguajes ignoran esto y toman como valor
inicial los bits que estaban en memoria, otros proveen un sistema de inicialización. Una variable solo se
puede usar si ya tiene valor.
 Binding Dinamica: El valor puede ser modificado por una operación de asignación.
 Binding Congelado (Estatico): Constante simbólica. Mantienen un valor constante.
Ejemplo en Pascal: const pi =3.1416
Ejemplo en ALGOL 68: real pi=3.1416
Diferencia entre Pascal y Algol 68: En Pascal el valor es un número o un string de caracteres, y es
posible establecer el binding en tiempo de traducción. El traductor puede legalmente sustituir el
valor de la constante por su nombre simbólico en el programa. En Algol 68 el valor puede ser dado como
una expresión que involucre otras variables y constantes: por lo tanto el binding puede ser solamente
establecido en tiempo de ejecución, cuando la variable sea creada.

Tipo: Es una especificación 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. En algunos
lenguajes el programador puede definir tipos nuevos.
 Binding Estático: El tipo es generalmente especificado por una declaración de variables. los
lenguajes con binding estático están orientados a la traducción.
Ejemplo, en Pascal : var x,y : integer;
z : boolean;
Ejemplo, en FORTRAN la declaración de la variable ALPHA, seguida de una sentencia tal como
ALPA=7.3, no sería detectada como un error; ya que se considera como la declaración implícita de
una nueva variable (ALPA).
Diferencia entre Pascal y FORTRAN: FORTRAN tiene reglas por default para determinar el
binding particular. Pero ambos asocian variables a tipos en tiempo de traducción y el tiempo del
binding es el mismo en los dos lenguajes.
 Binding Dinámico: Las variables no son declaradas, sino que su tipo es implícitamente
determinado por el valor que actualmente contienen. Los lenguajes con binding dinámico están
orientados a la interpretación. El binding dinámico provee gran flexibilidad en la creación y
manipulación de las estructuras de datos. Los programas son difíciles de leer.
Ejemplo: Alcance dinámico en APL:
A<-17 {A adquiere el valor 17 y tipo entero}
A<-42.3 {A adquiere el valor 42.3 y tipo real}
A<-[2 1 4 7 2 6 -1 -2 -3] {A adquiere esos valores y se vuelve un arreglo de 3x3}
-2-
no puedo saber el tipo de una variable hasta el momento preciso en que se ejecuta.

Almacenamiento: Es cuando la variable tiene un lugar físico de memoria. Almacenamiento Dinámico =


Almacenamiento Recursivo.
 Alocación: acción por la cual se reserva un área de almacenamiento para una variable.
 Alocación Estática: la alocación es realizada antes del tiempo de ejecución.
 Alocación Dinámica: la alocación se realiza en tiempo de ejecución.

Alcance: Ambiente, conjunto de sentencias que puede usar esa variable. Una variable es visible dentro de
su ámbito, e invisible fuera de el.
 Binding Estático de Ámbito: Las variables tienen un lugar físico durante la ejecución, se usen o
no, y se quedan en memoria hasta que el programa termine. Define el ámbito de una variable en
términos de la estructura léxica de un programa. Lenguajes: Fortran y Cobol
 Binding Dinámico de Ámbito: define el ámbito de una variable durante la ejecución del
programa. Lenguajes: BASIC, APL, LISP, y SNOBOL4.
Ejemplo: Alcance dinámico en Basic:
30 INPUT C
40 IF C<0 THEN GOTO 60
50 D=4
60 E=D+C
Si C=-3, no se conoce el valor de D => no se puede sumar; por lo tanto no sé si se puede usar el
valor de D en la sentencia 60. Solo cuando llego sé si se puede usar o no.

Clasificación de Lenguajes
Se clasifican de acuerdo a su comportamiento en tiempo de ejecución
Almacenamiento Estatico Lenguajes Estático

Clasificación Tipo y Alcance Estático Lenguajes Tipo Algol

Almacenamiento Dinamico
Tipo y Alcance Dinámico Lenguajes Dinamico
 Estáticos
 Almacenamiento estático.
 Son no recursivos.
 Asociados a compiladores.
 Ejemplo: Fortran y Cobol.
Estos lenguajes garantizan que los requerimientos de memoria para cualquier programa pueden ser
determinados antes del comienzo de la ejecución del programa. Por lo tanto, toda la memoria
necesaria puede ser ubicada antes de la ejecución del programa. Estos lenguajes no permiten la
recursión, porque podría requerir un número arbitrario de instancias y los requerimientos de
memoria no pueden ser determinados en tiempo de compilación (translation).
 Tipo Algol (Basados en Pila)
 Almacenamiento dinámico.
 Alcance estático.
 Tipo estático.
 Asociados a compiladores.
 Ejemplo: Algol 60, Algol 68, C, Pascal, Modula, Ada, etc.
Estos lenguajes permiten programas más potentes mientras los requerimientos de memoria no
pueden ser calculados en tiempo de compilación (translation). Sin embargo, el uso de memoria es
predecible y sigue un método del tipo LIFO .
 Dinámicos
 Almacenamiento dinámico.
 Alcance dinámico.
 Tipo dinámico.
 Son recursivos.
 Casi siempre relacionados a intérpretes.
 Ejemplo: LIST, PROLOG, APL, SNOBOL4.

-3-
En estos lenguajes, como el nombre lo indica, no se puede predecir el uso de memoria. Estos
lenguajes no pueden ser modificados por un SIMPLESEM (A Simple Abstract Semantic Processor –
un simple procesador semántico abstracto) basado en stack, porque permite al programador crear
objetos en puntos arbitrarios durante la ejecución del programa.Cuando mas dinámicos son los
lenguajes mas difícil resulta hacer un compilador.
Modelo de ejecución
Compilador: Traduce completamente del programa fuente al lenguaje máquina, y lo transforma en un
ejecutable cuyo código difiere del programa fuente. Lenguajes Estáticos y Tipo Algol.
Intérprete: Entiende la instrucción. El programa fuente queda exactamente igual. No encuentra los
problemas de gramática. Ejemplo: conserva el texto del programa fuente tal como lo escribió el
programador. Dinámico casi siempre relacionados a intérpretes.

Lenguajes Lenguajes
Estáticos Tipo Algol

Programa Programa
ejecutable ejecutable

Todo de
datos tamaño fijo datos  Sin tener en cuenta
memoria obtenida con
malloc, new o dispose.
frontera  No ocupan tamaño
variable definido o fijo.
 Durante la ejecución
de un programa puede
ser que no entre en
memoria.

No se usa

Lenguajes
Dinámicos

Intérprete

Programa
semitraducido

Tabla de
símbolos

Datos

-4-
Estructura de FORTRAN
Un programa en FORTRAN se compone de un juego de unidades, main y subprogramas. Se asegura la
cantidad de espacio que requiere cada variable local. Este se conoce tiempo de compilación y no se puede
cambiar en tiempo de ejecución. Cada unidad se compila separadamente y se asocia con un registro de
activación antes de la ejecución. Las variables se pueden crear antes del tiempo de ejecución y su tiempo
de vida se extiende sobre la ejecución entera del programa. El alcance de una variable se limita a la
unidad en la cual es declarada.
Las unidades pueden tener variables globales. Estas pueden ser vistas como que corresponden a un
registro de activación global a todas las unidades del programa.
Un programa debe pasar por ciertos pasos:
En el primero (compilación) cada unidad se traduce separadamente, sin conocer las otras unidades. Como
resultado todo se traduce a lenguaje de máquina, excepto que todas las referencias a memoria sean
compiladas de a pares (nombre de la unidad, offset).
En el segundo paso (enlace – linkeo) se enlazan las unidades. Como resultado todas las unidades se
asignan a una posición de memoria y todas las referencias a memoria se pasan a una dirección especifica.
El tercer paso (carga - load) consiste en cargar el programa en memoria y setear el punto de comienzo del
programa.
Describimos la semántica en términos de acciones especificas, lo que más nos interesa es la transferencia
de control entre unidades.
La primera posición de cada registro de activación se reserva para un puntero de retorno. La otra
información que se guarda son las variables locales (también debe contener información sobre los
parámetros).
Cada instrucción debe poder ser referenciada usando una pareja: el nombre de la unidad y el offset
(Desplazamiento).
En tiempo de enlace, como cada segmento se asigna a memoria, el par que refiere al registro de
activación o código de segmento debe traducirse a un número que es la dirección de memoria.
Como en el caso de FORTRAN, el tamaño del registro de activación y el OFFSET de cada variable local se
conoce en tiempo de compilación. El registro de activación no puede enlazarse al segmento de código de
la unidad estáticamente (antes de la ejecución) porque quizá se tomen acciones recursivas. Debe ser
enlazado para cada activación.

Estructura de Lenguaje Tipo ALGOL


Administración de memoria: Las variables tienen tamaño fijo. Las variables de un procedimiento
(función), se crean y desaparecen todas juntas. La característica principal es la estructura de bloque.
Bloques: Conjunto de sentencias con un delimitador de comienzo y de fin. La cual es usada para controlar
el ámbito de la variable.
 Disjuntos: No tienen porción en común. Las varibles locales de A no se pueden usar en B.
A
A
 Anidados: Un bloque está completamente encerrado dentro de la otro. B B
 Con nombre:
 Se los puede invocar en cualquier momento.
 No se ejecutan por secuencia, sino porque hay un llamado.
 Sí son recursivos.
 Las variables locales se crean y se inicializan cada vez que se llama al bloque.
 Ejemplo: Pascal

 Anónimos:
 Solo se ejecutan por sucesión, si la secuencia de control los encentra.
 No pueden ser recursivos porque no se pueden ejecutar ellos mismos.
 Un único nivel.
 Las variables locales se crean un sola vez, pero se inicializan cada vez que se utiliza el
bloque.
 No tiene registro de activación propio porque no tiene parámetros ni dirección de retorno.
 Ejemplo: C

-5-
Ejecución de Lenguaje Tipo ALGOL
Cada vez que un bloque entra en ejecución su registro de activación se activa. Dirección de retorno del
que lo llamo es lo que necesita conocer un bloque. Los parámetros se almacenan como si fueran variables
locales del llamado, no del llamador.
Las variables locales se crean implícitamente cuando la unidad se activa, y la cantidad de memoria
requerida para mantener los valores de cada variable local se conoce en tiempo de compilación.

Registro de Activación:
 Conjunto de datos necesitados para que un bloque se ejecute.
 El registro de activación contiene variables, a veces parámetros o a veces dirección de retorno (si
es el mian).

Un bloque no puede tener un anidamiento parcial, tiene que estar si o si dentro de un bloque.
A Busca en el entorno local, luego en el global y así sucesivamente hasta encontrarla.
B
D

C
A F

¿Qué pasa en la memoria cuando ocurre esto?

A->B->B->B->E->D Cada vez que se comienza a usar un procedimiento se adquiere memoria para
las variables del mismo.

Cuando se empieza a ejecutar A, se crea un bloque de memoria que contiene las variables de A, y otras cosas. Este
bloque de memoria recibe el nombre de registro de activación (todo lo que necesita un procedimiento para poder
ejecutarse).
Cuando A->B, se crea un registro de activación de B, y se
Programa guarda en una pila de registros de activación y así
ejecutable sucesivamente. Las variables de A se ubican en el
primer lugar disponible y las variables de B se ubican
variables de A más a continuación de A.
otras cosas Cada vez que empieza a ejecutar un procedimiento se
variables de B
adquiere memoria para las variables del mismo.
Pila de registro de Cuando termina D las variables ya no requieren esa
D variables de B activación zona de memoria, quedando disponible dicha zona.
variables de B Cuando termina E las variables ya no requieren esa
zona de memoria, quedando disponible dicha zona.
variables de E Si termina B, y luego el otro B, el B que queda en el
variables de D ejecutable llama a D, (B->D), las variables de D se
instalan arriba del 2° B. (zona gris).Por eso se llama
asignación dinámica.
¿Dónde se almacenan los parámetros?, se almacenan como si fueran variables locales del llamado, no del
llamador.

Registros de activación con tamaño estáticamente conocido:


En este caso, las variables locales se crean implícitamente cuando la unidad se activa, y la cantidad de memoria
requerida para mantener los valores de cada variable local se conoce en tiempo de traducción. Pascal (si ignoramos
los punteros) y C son dos lenguajes de esta clase.

-6-
Como en el caso de FORTRAN, el tamaño del R.A. y el desplazamiento de cada variable local dentro de él, son
conocidos en tiempo de traducción. En tiempo de traducción una variable puede ser ajustada sólo a su desplazamiento
dentro del R.A.; la asociación del espacio físico requiere conocimiento de la dirección del R.A., y esto sólo puede ser
hecho en tiempo de ejecución. Las variables de ésta clase se conocen como variables semiestáticas.
La reserva dinámica de los R.A. tiene dos consecuencias principales: permite la implementación de activaciones
recursivas de unidades y permite una utilización más eficiente del almacenamiento de datos. Los RA deben contener
suficiente información para identificar la instrucción a ser ejecutada y el RA que estará activo luego del retorno. Ahora
también reservaremos la posición en el desplazamiento 1 de cada RA para un puntero al RA de la unidad llamadora
(este puntero se llama el enlace dinámico). La cadena de enlaces dinámicos en el RA actualmente activo se llama
cadena dinámica y representa la anidación dinámica de las activaciones de las unidades.
Cuando una unidad U completa su instancia, su RA ya no se necesita más. 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 sólo a unidades que
están anidadas dentro de U y activadas luego de la activación de U. Estas unidades terminan sus activaciones antes de
que lo haga la instancia actual de U.

Registros de activación cuyo tamaño se conoce en la activación de la unidad:


En ALGOL, Ada, el tamaño del RA y la posición de cada variable local dentro del RA, se conocen siempre
estáticamente. Las variables son automáticamente creadas cuando se activa la unidad, pero su tamaño puede
depender de valores que son conocidos sólo en tiempo de ejecución cuando se activa la unidad. Tal es el caso de los
arreglos dinámicos: arreglos cuyos límites se conocen en tiempo de ejecución. Las variables de esta clase se llaman
variables semidinámicas.
Es que las variables locales no pueden ser asociadas a un desplazamiento constante dentro del RA en tiempo de
traducción; y el trabajo de asociación queda retardado a tiempo de ejecución.
Debido a que el número de dimensiones del arreglo se conoce en tiempo de traducción, el tamaño del descriptor se
conoce estáticamente. Todos los accesos a variables semidinámicas se trasladan como referencias indirectas a través
del puntero en el descriptor, cuyo desplazamiento se determina estáticamente.
En tiempo de ejecución, el RA se aloja en varias etapas. Primero, se aloja el almacenamiento requerido para las
variables semiestáticas y los descriptores de las variables semidinámicas. Cuando se encuentra la declaración de una
variable semidinámica, mediante las entradas de dimensión, se evalúa el tamaño real de las variables semidinámicas y
el RA se expande para incluir la variable. (Esta expansión 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 recién alojada.
El descriptor contendrá la dirección de comienzo de a, el límite inferior, y el límite superior en las posiciones m, m+1 y
m+2 respectivamente.

Registros de activación con tamaño variable dinámicamente:


Los lenguajes de la familia ALGOL (excepto ALGOL 60) desde PL/I y Pascal hasta ALGOL 68 y ADA, también permiten
al programador tratar con objetos de datos cuyo tamaño puede variar durante la ejecución del programa.
Consecuentemente, la cantidad de memoria requerida por un RA no se conoce cuando se activa la unidad. Tales
variables se conocen como variables dinámicas.
Un buen ejemplo de variables dinámicas es el arreglo flexible provisto por ALGOL 68 y otros lenguajes. Un arreglo
flexible es un arreglo cuyos límites pueden variar durante la ejecución del programa para acomodar el tamaño del
objeto que se le desea asignar.

new(p)

El tiempo de vida de un objeto creado de esta forma, a diferencia de las variables semiestáticas y semidinámicas, no
termina cuando se termina el bloque que contiene la sentencia de alocación. Algunos lenguajes (PL/I) contienen
sentencias para desalojar tales objetos explícitamente. Otros lenguajes (Pascal) establecen que los objetos viven
siempre y cuando una referencia a ellos (un puntero) exista. Además es posible crear varios objetos sin desalojar
ninguno de ellos. Es fácil ver, por lo tanto, que tales objetos no pueden ser alojados en el stack.
Resumiendo: las variables dinámicas significan objetos de datos cuyo tamaño y/o número puede variar
dinámicamente durante sus tiempos de vida. Este hecho prohíbe la alocación de estos objetos en el stack: ellos se
alojan en un área de memoria llamada heap. El término heap denota libertad de la connotación de estrategia LIFO del
stack). Por esta razón, las variables dinámicas son a menudo llamadas variables heap, contrariamente a las variables
semiestáticas y semidinámicas, las cuales son llamadas variables stack.

Pilas de registro de activación


Variable estática (Reg Base - Reg Cero): El programador no la conoce, es propia del lenguaje y apunta
siempre a la base del registro de activación corriente y se lo pone en un registro de la CPU. Sube y baja de
acuerdo al tamaño de la pila del registro de activación.
Ejemplo:
A tiene las variables h,i,j. h:=i+j

-7-
En Fortran (lenguaje estático)
Traducción del nombre a una dirección fija. El dato está en un lugar conocido.
MOV R1,2413 i Mueve el contenido de la dirección 2413 al registro 1.
ADD R1,1490 j Suma el contenido de la dirección 1490 al registro 1.
MOV 2FAO,R1 h Mueve el contenido del registro 1 en la dirección 2FAO.

En Tipo Algol
Traducción del nombre a una distancia fija del comienzo al registro de activación. El dato está en un lugar
desconocido.
MOV R1,[R0] 15 i i va a estar 15 lugares después del registro de activación.
ADD R1,[R0] 40 j j va a estar 40 lugares después del registro de activación.
MOV [R0] 9,R1 h h va a estar 9 lugares después del registro de activación.

Cadena dinámica: Cada puntero apunta al inicio del procedimiento que lo llamó, sirve para saber donde
volver cuando un procedimiento termina. Cada registro de activación tiene un puntero al registro que lo
llamó, la sucesión de punteros se llama cadena dinámica. La cadena dinámica permite que las variables se
puedan utilizar sin importar donde están.
Cuando A->B se genera: ADD R0, tamaño de A
Cuando B->E se genera: ADD R0, tamaño de B

¿Cómo volvemos hacia atrás?


Necesito el tamaño del registro de activación anterior o el comienzo. Se guarda en una dirección de A el comienzo de
B. En cada bloque se guarda el comienzo del que está más abajo en un lugar fijo dentro del casillero.

MOV R0,[R0] 8

Construcción de la cadena dinámica:


ABC

C
B
B C BP
A
Suponiendo que el tamaño de B se encuentra en la dirección 12 y el offset de la cadena dinámica es 8:

Comienzo del procedimiento C:


Para que el registro de activación de C apunte a B. Sumo el tamaño del registro actual a BP(registro cero) y lo tengo
apuntando al comienzo del siguiente

MOV BX, BP
MOV AX, BP
ADD AX, [BP]12 (tamaño de B)
MOV BP, AX
MOV [BP]8, BX

Final del procedimiento C:


BP(registro cero) vuelve a tener la dirección del comienzo de B.

MOV BP, [BP]8

Cadena estática: Conjunto de punteros donde cada uno apunta al registro de activación del
procedimiento que lo contiene en el árbol de anidamiento. Este tipo de estructura se usa para variables
globales.

No hay búsqueda porque no hay clave. Se accede siguiendo el puntero. (Pregunta de parcial).

-8-
Modelo de ejecución
A->B->B->C->D->E
Las variables se buscan en tiempo de compilación.
Para buscar una variable se busca en un entorno local y luego en uno global y así hasta encontrarla, sino
hay un error.
A
B
z A
x es variable global y está en C
z es variable global y está en A
C
x BD
AC
E B x:=y+z
Y Aes local

E y

Cada registro de activación tiene un


puntero que apunta a la base del
D registro de activación que lo llamó.
E Con este puntero no se donde están
las variables.

C x
D
E
Cadena estática
Cadena dinámica
B
D
E
A esta secuencia de punteros se
la conoce como cadena estática,
B
está asociada a los anidamientos
D
y la cadena dinámica está
E
asociada al orden

A z
D
R0 (registro cero)
E

El compilador en tiempo de ejecución arma el árbol de anidamiento y entonces sabe donde está cada
variable. Las variables se las busca en tiempo de compilación.

Construcción de la Cadena Estática: La cadena estática se utiliza para cuando se trabaja con variables
globales. Si en un registro de activación no están las variables para resolver la instrucción entonces hay
que buscarlas en los registros de activación de los padres de éste.
Esta cadena une cada registro de activación con su padre. Para formar la cadena estática el copilador
forma el árbol de anidamiento de los registros de activación y cuenta cuantos arcos se necesitan para
llegar a una variable de uno de sus ancestros.
También el compilador guarda un offset que es la distancia de la base de cada registro de activación a la
posición donde está la dirección del padre (cadena estática).

-9-
A
Ejemplo:
El offset de X en un registro de activación es 12,
para Y es 4B y para Z es 20 C X
D B
Si se que se ejecuta x=z+y => z está 1 nivel atrás
y está 2 niveles atrás E Y
x está 3 niveles atrás
MOV BX, BP
MOV BP, [BP]6 F Z
MOV AX, [BP]20
MOV BP, BX
MOV BP, [BP]6 G X=Z+Y
MOV BP, [BP]6
ADD AX, [BP]4B
MOV BP, BX
MOV BP, [BP]6
MOV BP, [BP]6
MOV BP, [BP]6
MOV [BP]12, AX
MOV BP, BX
Variables

Lugar Fijo Estáticas

Tamaño Fijo Lugar Variable Semiestáticas


< > Ejecución
Variable

Tamaño Variable Lugar Variable Semidinamicas


< > Ejecución < > Ejecución

Tamaño Variable Lugar Variable Dinamicas


Cualquier Momento

Tipo Variable Superdinamicas

Estáticas: tienen tamaño y lugar fijo en toda la ejecución del programa.

Semiestáticas: tienen tamaño fijo y lugar variable en distintas ejecuciones. Los limites (estáticos) no
varían no tienen porque esta en el R. A., los limites pueden estar en el código. Las variables solo
contienen datos. Pueden ser arreglos, punteros, estructuras, uniones. Ej: A[4]

var SE DATOS En tiempo de compilación

Para el acceso a las variables semiestáticas, se requiere un offset a las variables semiestáticas.

Semidinámicas: tamaño variable y lugar variable en distintas ejecuciones. Son siempre arreglos con
limites variables. El tamaño varia en el momento preciso que se crea el R. A. y no varia mas. Las variables
contienen datos y los limites de los arreglos, que se los conoce como descriptores. Los limites
(descriptores) son semiestáticos no cambian de tamaño pero si cambian de valor. En los registros de
activación se separan los limites (Descriptores) de los datos. Ej: A[i]
Descriptores Constantes
var SD 4,17,8,22 DATOS En tiempo de ejecución
- 10 -
Lenguajes Tipo Algol
:
DATOS VAR SD 2

En Algol, no importa el DATOS VAR SD 1


nombre porque se pisa al puntero
compilar DESCRITOR VAR SD n

DESCRITOR VAR SD 2

DESCRITOR VAR SD 1

VARIABLES SEMIESTÁTICAS

El acceso a las variables semidinámicas requiere el acceso al descriptor para buscar el dato. La distancia
al descriptor la conoce en tiempo compilación, en tiempo de ejecución se conocen los datos. Variables
semidinámicas tienen más flexibilidad y consumen mayor tiempo.

Dinámicas: pueden cambiar de tamaño en cualquier momento y tienen ubicación variable.


 Anónimas: Es el usuario el que se encarga de la administración del HEAP, pide, devuelve
memoria, y cambia su tamaño cuando quiera. El usuario tiene un puntero a las variables
dinámicas que no tienen nombre. En C o Pascal: malloc, new y dispose, free, delete.
 Con nombre: Es el lenguaje (RUNTIME PACKAGE) el que se encarga de asignar y liberar
memoria en el HEAP. Aparecen en Algol.

Tamaño constante
En VD descriptor datos Variable

Registro de Activación

En VD
HEAP

Descriptores de VD Datos

Las VD no se guardan en el registro de activación, porque pueden cambiar de tamaño en cualquier


momento. Si las VD son con nombre tienen descriptores que están en la pila, y los datos están en el
HEAP. Si las VD son anónimas solo está el puntero el R A.

Superdinámicas: Pueden cambiar de tamaño en cualquier momento y tienen ubicación variable. Tienen
tipos variables (solo se sabe en tiempo de ejecución). No se almacena ni la variable, ni su descriptor en el
registro de activación. No las soportan los lenguajes tipo pila como Algol, Pascal, C, C++. Existen solo en
los lenguajes dinámicos.

Nombre Tipo Otras cosas Dirección de Datos ( Tabla de Símbolos )

Prog. Semitraducido
- 11 -
Tabla de Símbolos
Leng. Dinamicos, el nombre esta en la tabla símbolos. El nombre
esta presente en tiempo de ejecución.
LISP, APL, SNOBOL4, PROLOG.

¿Qué tipos de variables usan los lenguajes orientados a la pila?¿y los dinámicos?
Lenguajes orientados a la pila --- VAR semiestáticas y semidinámicas. Descriptor de las Var Dinamicas.
Lenguajes dinámicos ------------- VAR superdinámicas.

¿Qué tipos de lenguajes poseen variables que pueden cambiar de tamaño en tiempo de
ejecución?
Los lenguajes dinámicos pueden tener variables que cambien de tamaño en tiempo de ejecución. Y
lenguajes tipo algol si utilizan variables semidinámicas o dinámicas.

¿Qué tipos de variables pueden cambiar de tamaño en tiempo de ejecución y dónde se


almacenan?
Las semidinámicas cambian de tamaño solos cuando se crea el registro de activación, es decir el
comienzo de la ejecución del bloque. Y se almacena en el R A como un descriptor mas el dato.
Las las dinámicas pueden cambiar de tamaño en cualquier momento de la ejecución. Poseen un
descriptor en el R A si son con nombre, y en caso de ser anónimas existe solo un puntero en el R A. Y los
datos se almacenan en el HEAP.
Las superdinámicas (además cambian el tipo) se almacena el puntero, el tipo, el nombre y otras cosas
en la tabla de símbolos y los datos en el HEAP.
¿Quién le otorga el espacio en memoria?
En Algo el RUNTIME PACKAGE es el que otorga el espacio de memoria y Pascal el usuario.

¿Dónde se alojan las variables tipo puntero? ¿Por qué?


Las variables de tipo puntero se almacenan en el registro de activación, debido a que son variables
semiestáticas. Y se requiere un offset para el acceso a las variables semiestáticas. Las VD anónimas se
guarda el puntero en RA y el dato en el HEAP, porque puede cambiar de tamaño en cualquier momento.

Porque se dice que las variables semidinámicas solo pueden ser arreglos o estructuras mas
complejas.
Son siempre arreglos (o estructuras mas complejas) con limites variables. Y estos limites (descriptores)
son semiestáticos no cambian de tamaño pero si cambian de valor. Entonces las variables semidinámicas
tienen tamaño variable y lugar variable en distintas ejecuciones. Ej: A[i]

- 12 -
¿Quién se encarga de asignar el espacio en memoria de las variables que se almacenan en el
HEAP, para Pascal y Algol?
En Algol el que se encarga de otorgar espacio en memoria es el RUNTIME PACKAGE. Y en Pascal el que se
ocupa de asignar espacio es el usuario.

¿Qué diferencia tienen las variables dinámicas de Pascal con las de Algol?¿Qué similitud?
La diferencia es que Pascal tiene compatibilidad de tipo por nombre y Algol por estructura.
La similitud es que tienen alcance y tipo estático, cambian de tamaño en tiempo de ejecución.
Almacenamiento dinámico, no ocupa un tamaño fijo en memoria.

¿En que casos el nombre de una variable está presente en tiempo de ejecución?
Cuando esta se trata de variables superdinámicas. Esto es así ya que existe una tabla de símbolos para
mantener y administrar este tipo de variables en memoria.

¿Pueden las variables semiestaticas poseer limites estaticos?


Las variables semiestaticas tienen sus limites estático, si no los tendría serian variables semidinamicas.
Ejemplo: Variable semiestatica A[4] (los limites están en el código) - Variable semidinamica B[i].

¿Donde se almacena los limites de los arreglos?


Si hablamos de variables estáticas los limites se almacenan en el código. Y si hablamos de variables
semiestaticas y semidimamicas los limites se almacenan en el R.A. Y tanto para los lenguajes que verifican
limites como los que no verifican limites. Las semiestáticas pueden almacenarse en el código.

¿Los limites de los arreglos están en algún lugar de memoria en tiempo de ejecución?¿El
lenguaje verifica limites?
El programa tiene los valores guardados en algún lugar de memoria, entonces el lenguaje verifica límites.
Si el lenguaje no verifica límites, no lo guarda, después lo interpreta.
Si el lenguaje verifica limites, y hablamos de variables semidinámicas, el ejecutable necesita conocer
todos los limites (porque puede cambiar el R.A.). Matriz (4 limites), array (2 limites).
Si el lenguaje verifica limites, y hablamos de variables semiestáticas, el ejecutable necesita conocer
para Array 1 limite (limite inferior) y para Matriz 3 limites.
Ej Z [7..45,9..22] - Por Filas (7,9,22) - Por Columna (7,45,9)

Vemos la memoria como una matriz: integer q [4..22,8..17]


q4,8 q4,9 …………….q4,17
q5,8 ………………….…q5,17
q [i,j]

q22,8 ………….…………q22,17
la dirección de q [i,j] es igual a la dirección de q [4,8] por que por filas o por columnas siempre es el
primero.

Matriz organizada por filas

dirección q [i,j] = dirección [4,8] + tamaño de cada elemento x [(i-4) * (17-8+1) + (j-8)]

cantidad de filas
cantidad de elementos de cada fila
cantidad de elementos que hay antes en la fila

en tiempo de ejecución necesito conocer el 4,8 y 17.

- 13 -
Matriz organizada por columnas

dirección q [i,j] = dirección [4,8] + tamaño de cada elemento * [(j-8) * (22-4+1) + (i-4)]
Necesito conocer los dos comienzos y 1 final (4,22 y 8).

¿Por qué no existe garbage en los lenguajes dinamicos?


En los lenguajes dinámicos, todos los datos están referenciados por un símbolo en la tabla de símbolos.
Toda zona de memoria que no está referenciada por un símbolo en la tabla de símbolos se considera libre
para ser utilizado. Por esta razón no hay zona que contengan datos sin ser refenciados por la tabla de
símbolos, por lo tanto no existe garbage.
Lo que se considera como garbage en los lenguajes dinámicos es la fragmentación, producida por la
asignación no contigua de memoria.

¿Puede el MAIN en un lenguaje con variables semidinámicas tener un registro de activación de


diferente tamaño en diferentes ejecuciones?
No. MAIN es una palabra clave. No hay sentencias de asignación en ejecución. No hay programas fuera del
MAIN. Puede haber variables fuera del MAIN, pero no tienen valor.
Entonces, las variables semidinámicas tienen variables como parte de los límites y tienen que ser globales
y haber adquirido valor.

Los bloques anónimos de “C” {} no requieren un registro de activación propio, pese a que
tienen variables locales.
Verdadero. Los bloques anónimos no tienen nombre, por lo que no tienen parámetros, ni dirección de
retorno. Por consecuencia, no pueden ser recursivos. No voy a tener nunca dos registros de activación
para un bloque anónimo. No necesito registro de activación propio, porque al no repetirse, puede estar en
el registro de activación de otro archivo “.C”. Esto hace al programa más eficiente. Si tendría un registro
de activación por cada FOR o WHILE, ocuparía más lugar y tendría más accesos a memoria. La mayoría de
los C no asignan registros de activación por eficiencia.

El tiempo de vida de una variable entera de un bloque de “C” {} no necesariamente coincide


con su alcance.
Verdadero, el tiempo de vida es mayor porque viven durante todo el tiempo de la función. La variable
vive más tiempo que el se puede usar.

El tiempo de vida de una variable declarada en un programa en lenguaje Pascal, sin llamadas a
procedimientos ni funciones, coincide siempre con su alcance.
Verdadero, no tiene procedimientos ni funciones. El tiempo de vida no tiene nada que ver con el alcance.

Teniendo en cuenta el alcance y el tiempo de vida, refleje la diferencia entre ambos en un


programa escrito en C.
Int main() void funcion()
{funcion(); { static int a=0;
funcion(); a++;
return 0; }
}
La diferencia se muestra cuando termina la ejecución de “funcion()”, ya que la variable estatica “a” sigue
manteniendo su valor (su tiempo de vida). Pero es local a “funcion()”, su alcance termina cuando termina
“funcion()”. Por lo tanto, no se puede ser accedida por el main().
Cuando se ejecuta “a++” en la segunda llamada a “funcion()”. “a” va a valer 2. Ya que mantuvo su valor
por ser estática.

Dado el siguiente fragmento del programa. Clasificar de acuerdo a su binding.


Int a; char h[30];
int *c,*d,*I,*j;
int **g;
cin>>a;
void procedim(int j)
{c:=(int *) malloc (sizeof(int));

- 14 -
char f[a];
int c[a][3];
int c[a][3] :={205,306,405,101,125,115};
d:=(int *) malloc (200);
I:=(int *) malloc (100);
}
Tipo: De acuerdo a las operaciones del programa se puede decir que todas las variables tienen tipo
estatico (no cambian durante la ejecución).
Alcance: Todas las variables son de alcance estático ya que se sabe que instrucciones pueden hacer uso
de cada una de ellas (tanto si son globales como locales).
Valor: El valor de todas las variables es dinámico, es decir, se conoce en tiempo de ejecución.
Almacenamiento: Como el lenguaje permite variables semidinamicas “f[a]” el almacenamiento debe ser
dinámico ya que las posiciones tienen que poder variar de un ejecución a otra.
Para el caso de los punteros (int *c, *d, *j, *i), el puntero es una variable semiestatica (está en la pila, no
puede cambiar de tamaño pero se de posición de una ejecución a otra) pero el bloque de memoria en el
HEAP apuntado por el puntero.

¿Qué implica el tipo estático y el alcance estático?


Implica: Que las variables tienen una característica razonablemente estática.
 Que las variables son bastante conocidas.
 Tamaño fijo.
 Las variables de un procedimiento o una función, se crean y desaparecen todas juntas.
 Aparecen en bloque.
 No son recursivos.
 Asociados a copilador.

Una variable declarada en C ó C++ como static posee almacenamiento y alcance estáticos.
Verdadero, tiene alcance y es estático.

Si una variable definida en el interior de un bloque de “C” {} contiene una inicialización, por
ejemplo, int x=3; la misma se ejecuta cada vez que comienza el bloque.
FALSO.
Bloque anónimo: La variable se crea una sola vez, pero se inicializa cada vez que se utiliza el bloque.
Bloque con nombre: Se crea y se inicializa cada vez que se llama al bloque.
Las inicializaciones en “C” solo se ejecutan cuando se crea el registro de activación.
FALSO.
En los bloques anónimos en C, las variables se crean una vez y se inicializan muchas veces.

Si un lenguaje tiene variables dinámicas su RA puede cambiar de tamaño.


FALSO. La variables dinámicas no se guardan en el RA porque cambian de tamaño en cualquier momento.
En el RA se guarda el descriptor que apunta a la variable dinámica que esta almacenada en el HEAP.

Si un lenguaje tiene variables semidinamicas su RA no cambia de tamaño.


FALSO. Puede cambiar el tamaño de los datos pero el descriptor tiene tamaño fijo.

Los lenguajes tipo Algol no se pueden compilar.


FALSO. Traduce completamente el programa fuente en lenguaje máquina y lo traduce en un ejecutable
cuyo código difiere del programa fuente.

Los lenguajes Dinamicos no se pueden compilar.


Verdadero. Están relacionados a interprete. Entiende la instrucción pero el programa fuente queda
exactamente igual como lo escribió el programador. No encuentra errores de gramática.

¿En que casos el puntero de la cadena dinámica apunta al mismo lugar que el puntero de la
cadena estática?
Cuando cada padre llama a un hijo y este a otro y así sucesivamente. Es decir llama a alguien en forma
local.

¿Cómo se construye la cadena dinámica?

- 15 -
Cada puntero apunta al inicio del procedimiento que lo llamó.
¿Para que se usa?
Sirve para saber donde volver cuando un procedimiento termina. La cadena dinámica permite que las
variables se puedan utilizar sin importar donde están.

Porque los lenguajes con alcance dinámicos necesitan un tabla de símbolos en tiempo de
ejecución.
Los lenguajes con alcance dinámicos define el ámbito de una variable de acuerdo a la ejecución del
programa. Dicha tabla tiene la información de las variables del programa, como nombre, tipo, y los
punteros que apuntan al área de datos.

Explicar porque el uso de variables semidinámicas es más lento que el uso de variables
semiestáticas.
A las variables semidinámicas hay que acceder primero al descriptor, quien contiene los límites y un
puntero a la variable.

Explicar porque el uso de variables globales de la pila es más lento que el uso de variables
locales.
Porque el alcance de las variables locales es mas chico que el alcance de las variables globales.
¿Es también para las variables estáticas?

Las variables semiestáticas poseen límites estáticos


Verdadero, los límites son constante. Por lo tanto estos tienen tamaño fijo y ubicación fija.

Pasaje de parámetros
Es la transferencia de datos entre un método invocado y un método/procedimiento invocador.
Tienen ventajas en términos de legibilidad y modificabilidad. La mayoría de los LP usan un método posicional para
asociar los parámetros actuales a los formales en las llamadas a los subprogramas.

Tipos de parámetros:
 Formales: Son aquellos que se utilizan en el momento de definición.
Ejemplo:
int pepe (int x,int y)
variables locales de la función,
aunque de alguna manera provienen de afuera.
 Reales: Datos del llamado que se entregan en cada ejecución.
Ejemplo:
pepe (3,b)
parámetros reales

Sintaxis del pasaje de parámetros: ¿Quién con quien? El pasaje de parámetros es la asociación de los parámetros
formales y reales.
 Por Posición
 Completa
 Faltante
 Al final.
 En cualquier lado.

 Explícita: pepe (use 3 as b, use 4 as c). En ADA: pepe (3=>b, 4=>c)


 Anónimas: no digo cuales van a ser los parámetros.
int pepe (float x, …)
printf (“%s, …,%d”,x,y)

Técnicas de pasaje de parámetros de asociación (parámetros formales y reales)


Por posición: La misma cantidad de parámetros formales y reales.
1° pf con 1° pr, 2° pf con 2° pr ……
Ejemplo: int pepe (int a, int b, int c);

 Sin faltantes. Pepe (2, 3, z)


 Faltantes al final. Pepe (2, 3 )
- 16 -
 Faltantes en cualquier lugar. Pepe (2, z) Pepe (3, z)

 Explícita: La asociación se indica bien claramente.


Ejemplo: pepe (use 3 as b, use 4 as c); //3 se asocia con b y 4 con c.

Esta técnica casi siempre ha sido opcional.


En Ada la invocación se hace así:
pepe (3 => b, 4 => c) //Las comas ya no indican posición sino que solo separan.
pepe (4 => c, 3 => b)

 Anónimas: Son aquellas en que no digo cuales van a ser los parámetros.
Ejemplo: int pepe (float x, …)
int printf (char * fint, …)
pepe (7.2)
pepe (7.3, ‘b’, 22, “texto”, …)
se pueden poner muchos o ningún parámetro.
printf (“%s, …%d”, x,y);
printf (“%s,%d”, x,y,z); //Solo imprime X e Y.

Semántica del pasaje de parámetros: Como se asocia (no quien se asocia con quien)
(LLAMADOR) A->B (LLAMADO)

B
A x

 Referencia: B(x), existe un solo x y está en A (llamador), el parámetro real es el único que existe, y el formal no
existe, solo es otro nombre para x (alias o sinónimos). La unidad llamadora pasa a la unidad llamada la dirección
del parámetro actual, se trata como una referencia a la posición cuya dirección se pasó. Estándar de Fortran.
Pascal permite pasar valores por referencia.
 Nombre: El mecanismo es muy similar. Tampoco existen 2 parámetros (copias textuales). Cada ocurrencia del
parámetro formal se reemplaza textualmente por el parámetro actual. Estándar en Algol 60.
 Copia: Existen 2 parámetros, el real y el formal. Ambos tienen ubicaciones distintas y están en registros de
activación diferentes.

B z
A x

Los parámetros formales no comparten almacenamiento con los parámetros actuales; sino que actúan como variables
locales. Así, la llamada por copia protege a la unidad llamadora de modificaciones inadvertidas de los parámetros
actuales.
El pasaje de parámetros por copia tiene tres variantes:
 Por valor: Se utiliza a la ida cuando los datos van del llamador al llamado. La unidad llamadora evalúa los
parámetros actuales, y estos valores se usan para inicializar los parámetros formales, los cuales actúan
como variables locales en la unidad llamada. La llamada por valor no permite ningún flujo de información
de retorno al llamador, así que las asignaciones a los parámetros formales no afectan a la unidad
llamadora. Pascal permite pasar parámetros por valor.
 Por resultado: Se copia a la vuelta, la unidad llamada termina y devuelve el dato. las variables locales
correspondientes a los parámetros formales no son seteadas en la llamada al procedimiento, pero su valor,
al retorno, se copia en la posición del parámetro actual en el ambiente del llamador. La llamada por
resultado no permite ningún flujo de información a la unidad llamada.
 Por valor/resultado: Se copia a la ida y a la vuelta. , las variables locales que denotan parámetros
formales son inicializadas en la llamada al subprograma (como en la llamada por valor) y al retorno,
copian sus valores a los parámetros (como en la llamada por resultado). La llamada por valor-resultado y
la llamada por referencia pueden tener efectos diferentes.

Ejemplos de pasajes de parámetros:

Programa modelo:
a:array[15] int;
h:int; //h es global.
procedure parte 1 (x, y:int)
h:=h+1;

- 17 -
x:=x+1;
print (“H”, h, “X”, x, “Y”, y);
fin

{main}
h:=1;
a[1]:=2, a[2]:=3, a[3]:=4;
parte1 (h, a[h]);
print (“h”, h, “a[h]”, a[h]);

1. Referencia:
Pasa como parámetro la dirección de memoria, entonces la variable local queda apuntando a esa dirección de
memoria.

h-> 1 2 3
a[1]->2

3,3,2 -> parte1


3,4 -> main

2. Por nombre:
Reemplazo el nombre dentro de la función. Primero se hace un reemplazo textual y después se ejecuta.

h=h+1 2
h=h+1 3

h, h, a[h]

3, 3, 4 -> parte1
3, 4 -> main

3. Copia resultado:
Primero se inicializan los parámetros en cero. Y una vez terminada la función se copian los resultados de los
parámetros formales hacia los reales.

Se inicializan los parámetros del procedimiento


x=0
y=0

h=h+1 2
x=0+1 1

2,1,0 -> parte1


Y una vez terminada la función se copian los resultados de los parámetros formales hacia los reales.
h<-x
a[h]=a[1]<-y El vector quedaría:

1,0 -> main a[1]=0 a[2]=3 a[3]=4

4. Copia valor:
Copia parámetros formales con el valor de los parámetros reales.

procedure parte 1 (x=1, y=a[h]=a[1]=2)


x=1
y=2

h=h+1 2
x=1+1 2
El vector quedaría:
2,2,2 -> parte1
2,3 -> main a[1]=2 a[2]=3 a[3]=4

5. Copia valor-resultado:
Hace ambas cosas.

- 18 -
Copia parámetros formales con el valor de los parámetros reales.

procedure parte 1 (x=1, y=a[h]=a[1]=2)


x=1
y=2

h=h+1 2
x=1+1 2

2,2,2 -> parte1


Y una vez terminada la función se copian los resultados de los parámetros formales hacia los reales.
h<-x
a[h]=a[1]<-y El vector quedaría:

2,3 -> main a[1]=2 a[2]=3 a[3]=4

En los pasajes por copia que devuelve resultado (copia resultado y copia valor-resultado), tiene el siguiente error:
{main}
h:=1
parte 1(a,a)
procedure parte 1(x, y) Error, hay dos valores resultado
x=x+1; para a y no se sabe cual tomar.
y=y+3;

Ejemplo de Fortran (por referencia):

SUBRUTINE ZZ (I,J)

I=22

END

M=8
ZZ (M,N) ZZ (12,N)
K=M //K vale 22 K= 12*2 //24

L=12 //22
“L” resive el valor 22 porque altera el lugar donde esta el 12.
Porque las constantes tienen un lugar fijo en memoria y
si las altero, guarda el último valor.
ZZ(8,N)
K=8 //K vale 22

Ejemplo de Algol (por nombre):


Fue la solución al problema del pasaje por referencia, en esa época estaba de moda el macro assembler, macros de
las que existe una noción de reemplazo de parámetros textual.
La idea de Algol está sustentada en al idea del reemplazo textual.

proc zz (int x,y) void

El reemplazo del parámetro real al formal se hace textualmente


x:=x+y;

zz (a,b) zz (w, c*d)


a:=a+b; w:= w+c*d se ejecuta

zz (12,h)
12:=12+h //error ya que el compilador de Algol no me deja poner constantes a la
//izquierda.
zz (h,12)
h:=h+12 //OK

- 19 -
Introdujo un nuevo problema que hace muy difícil descubrir los errores.

proc swap (int x, int y) : void


begin
int tmp ;
tmp:=x ;
x:=y;
y:=tmp;
end;

intercambio del índice con el contenido del arreglo.


swap (i,a[i]) swap (a[i],i) si a[i]
tmp:=i; 4 tmp:=a[i]; 9 12,7,3,9
i:=a[i]; 9 a[i]:=i; a[4]=4 i:=4
a[i]:=tmp; a[9]=4 i :=tmp; i:=9
i a[4] tmp a[9] i a[4] tmp
4 9 4 4 4 9 9
9 9 4

El programa se comporta diferente cuando se cambian los argumentos de lugar.

¿Cómo influye el uso de variables semidinamicas en el pasaje de parámetros por nombre?


El uso de variables semidinamicas en el pasaje por nombre puede generar resultados diferentes siempre que los
parámetros estén acoplados. Por ejemplo la función SWAP para intercambiar dos datos.
Si tengo un vector “a” a[1] e i=1

Si llamo a la función asi SWAP(i,a[i]), entonces


swap (i,a[i])
tmp:=i; // tmp=1
i:=a[i]; // i=5
a[i]:=tmp; a[5]  No es lo que quiero
En cambio si lo llamo como SWAP (a[i],i)
tmp:=a[i]; //tmp=
a[i]:= i; //a[i]=1
i:=tmp; //i=5  Intercambio correcto

Tipos de datos

 Predefinidos (built-in): Construido por el lenguaje. Integer, real, float, etc.


 Definidos por el usuario (user defined). (de Algol en adelante)

Fortran no tenía definición de tipos de datos. Escasez de tipos de datos.


Ante la pobreza de datos de Fortran surgió:
 PL1: gran cantidad de datos predefinidos en el lenguaje.
 Algol: redujo la cantidad de datos y ofreció la posibilidad que el usuario definiera sus propios tipos de
datos. La solución fue la gran característica de este lenguaje.
Hoy día todos los lenguajes se caracterizan porque se le de la posibilidad al usuario de crear datos.

Formas de construir tipos de datos

 Producto cartesiano:
Es un juego de elementos ordenado por tuplas. Puedo definir I*R*C (entero*real*carácter) => es una variable
válida.
En Pascal son los record. En C son los struct.
La mayoría de los lenguajes tienen la posibilidad de tener producto cartesiano.

 Arreglos:
Una sucesión de tipo de valores donde se le asocia un número entero que actúa como índice. Usa un índice con un
valor que no pertenece al dominio.
En Ada se acepta un mapeo de las enumeraciones sobre otro tipo. Ejemplo: Gastos [mar] lun, mar, mierc.
Enlace de una sucesión a un tipo:

- 20 -
 En tiempo de compilación: soporta variables estáticas y semiestáticas. Ejemplo: FORTRAN, C y PASCAL.
 En tiempo de creación: se hace en tiempo de ejecución cuando se crea la variable. Son arrays
dinámicos. Estos son de tipo semidinámicos (¿?). Ejemplo: ALGOL 69, SIMULA67.
 En tiempo de uso: es el más flexible y el más costoso. El tamaño del array puede cambiar en tiempo de
ejecución. Es típico de lenguajes dinámicos como SNOBOL4 y APL.

 Secuencias:
Ejemplo: STRING, FILE. Un número de ocurrencias seguidas de un cierto tipo de datos. Difícil manipulación y
problema de almacenamiento (dinámico), porque no se conoce su tamaño.

 Recursión:
No se invoca n veces, sino una sola. Pueden contener componentes que alarguen el mismo tipo.
Ejemplo: Un árbol binario. Una lista doblemente enlazada usando nodos.
{typedef struct pepe
int x;
char z;

pepe *q;} uso lo que estoy definiendo como parte de la definición.


En la definición recursiva, sólo se puede utilizar para definir punteros. Recursividad en tipos de datos se restringe a
punteros.

 Uniones:
Agregado de datos producido cuando 2 o más datos ocupan el mismo lugar en diferente momento.
¿Para qué sirven las uniones? Hace mas flexible el código.
 Ahorro de espacio, memoria.
 Dar soporte de tipos dinámicos en lenguajes tipo Algol.
Ejemplo:
unión (int,float) x; (lo que digo es que x puede cambiar a int ó float y nada más).

El compilador de Algol o C reserva el tamaño para el tipo de datos más grande.


Discriminante: 1 o 2 bytes pegados a la variable. Se usa para saber cual es el tipo de valor almacenado por una
unión. Funciona como descriptor.

En Pascal: A las uniones se las llama registros variantes. Estos tienen discriminantes, la variable es explícita, las
uniones en Pascal son inseguras, puedo guardar un real teniendo guardado un entero.
En Algol: A las uniones se las llama uniones. Estas uniones tienen discriminantes y este discriminante es implícito, el
programador no lo ve. Unión segura. El compilador no me deja equivocar.
En Ada: Las uniones son registros variantes que tienen discriminante y éste es explícito y se trata de una unión
segura.
En C: Son uniones, no tienen discriminante y son uniones inseguras. Escribo un discriminante o nó según el
programador quiera.
Ejemplos:
En Algol: unión (int,real) x;
En C: unión (int x,real y) z;
_El compilador reserva 4 bytes (2bytes para int y 4 bytes para real).
_Si hay un int desperdicio una parte.
_Si hay un real uso todo
En Algol reserva 5 bytes, 4 para el real o el int y 1 para el discriminante (pista del tipo almacenado).

Si int => i Sin real => r

En Algol: z:=2 i 2 a:=x las variables unión no pueden estar del lado derecho.

En C: z:=2 2 a:=z ¿Cómo sé si hay almacenado un real o un int?. No lo


Sé. Por la variable misma nos dá que hay guardado.
En Algol: si “z” es de tipo unión y “a” de cualquier tipo, no puedo hacer “a:=z”.
En C: Es fácil tener problemas con las uniones.

Cláusula de conformidad de Algol: Permite al lenguaje tener uniones seguras. Para usar un dato de una unión se
interroga sobre el tipo de dato. Solo se puede usar la unión (a la derecha) dentro de una clausura de conformidad
CASE. Las uniones por si solas son inseguras. Por eso Algol verifica los tipos de datos.
Ejemplo: Sumar una unidad a una unión en Algol

unión (int,real)x;

- 21 -
case x in
when int x: x:=x+1
when real x: x:=x+1.0
esac

¿Como se asegura Algol que las uniones no creen conflictos?


Usando la cláusula de conformidad.

Unión en PASCAL
record producto;
número:integer; parte común
pventa:real;
discriminante explícito: lo tengo que
escribir.
case stock: bolean of
true: (cant:integer);
false: (ordenado:real, unión Parte variante con un discriminante
esperado:real) lamado stock (representa la unión)
end
p1:producto;
p1.número:=10;
Me indica que las uniones son
p1.stock:=true;
p1.ordenado:=3.5; uniones inseguras inseguras en Pascal ya que uno
tendría que guardar una cantidad
entera y no una real.

Si un lenguaje tiene discriminante obligatorio en sus uniones, las uniones son seguras.
FALSO. Pascal utiliza discriminante explicito que representa la unión y las mismas son inseguras.

Registro variante en ADA


Type PRODUCTO (STOCK:boolean) is
record
NÚMERO:integer;
P.VENTA:float;
case STOCK of
when TRUE => CANT:integer;
when FALSE => ORDENADO:float; Ada no me permite alterar el valor
ESPERADO:float; de una variable que está en la parte
end case variante.
end record

p1, p2, p3:PRODUCTO;


P1.CANT:=7; ERROR

p1:=( , ,TRUE,7);
Es correcto ya que el compilador
p2:=( , ,FALSE,7.3,8.5);
p3:=p1; verifica que toda la parte variante
sea consistente.

El compilador verifica que la parte variante sea consistente, siempre se maneja con registros variantes consistentes,
por lo tanto las uniones en ADA son seguras. Nunca se acepta un valor inconsistente.
El discriminante puede ser cualquier enumeración, nó únicamente booleano.
Registro variante congelado en ADA: No puede cambiar de variante en ningún momento de la ejecución, al
discriminante se le da un valor fijo.
p4:PRODUCTO(false);
Cuando se declaran variables hay que especificar obligatoriamente el valor del campo discriminante (excepto en la
declaración de parámetros formales), una vez hecha la declaración no se puede cambiar.

¿Cómo se asegura ADA que las uniones no creen conflicto?


ADA se asegura que las uniones no creen conflictos mediante el uso de un descriptor explícito con nombre. No permite
el compilador que se cambie el topo de la variable si no se cambia también el descriptor.
Ejemplo: TYPE UNION (t: BOOLEAN) IS
RECORD
CASE t IS
WHEN TRUE => x: FLOAT;

- 22 -
WHEN FALSE => x: INTEGER;
END CASE
END RECORD
Entonces, para cambiar: x => (t=>TRUE; x=>3,4) (en la misma instrucción!)

¿Cuáles son las limitaciones para el uso de registros variantes congelados en ADA?
No puede cambiar de variante en ningún momento de la ejecución, al discriminante se le da un valor fijo. Si cambio el
discrimínate tengo que cambiar el dato.

Diferencia entre Pascal, Ada, C, Algol.


En Pascal se puede modificar el discriminante y no los datos, entonces las uniones son inseguras.
En Ada eso no se permite, si cambio el discriminante tengo que cambiar el dato, las uniones son seguras.
C no utiliza discriminante, ni nada que permita saber que tipo de dato contiene la variable. Las uniones son
inseguras.
Algo tiene discriminante, entonces las uniones son seguras.

LENGUAJE NOMBRE DISCRIMINANTE SEGURIDAD


Pascal Registros Variantes Explicito (Con Nombre) Inseguras
C Uniones No Tiene Inseguras
Algol Uniones Implícito (Anónimo) Seguras
Ada Registros Variantes Explicito (Con Nombre) Seguras

Puede una variable de tipo unión o registro variante, almacenarse en el heap.


Si, mientras sean lenguajes permitan utilizar variables dinámicas. Ejemplo: C, C++, Pascal, ADA.

¿Puede una variable de tipo unión pasarse como parámetro, si se pasa por referencia?¿Y por nombre?
Puede pasarse por referencia, pero hay que tener cuidado con el uso que se le da en el procedimiento, porque las
uniones no pueden ser usadas del lado derecho de una asignación(Exepto en Algol si uso la Cláusula de
Conformidad).
El pasaje por nombre también es válido pero hay que tener cuidado dentro del procedimiento como se usa.

Asignaciones
a:=b En C -> edevalio:=errevalio

valor (tiene que ser del mismo tipo de la celda apuntada por la dirección)(evalue).
dirección (lvalue)
Según Algol: Las variables son referencias a celdas. Cuando mencionamos la variable hablamos de la dirección y nó
del contenido.

int x; (variable entera).


ref int y; (puntero a entero).
ref ref int z; (puntero a puntero de entero).

x es la dirección de un entero.
y es la dirección de un puntero a entero el cual a su vez tiene una dirección de otra celda que es un entero.
z es la dirección de una celda que a su vez es un puntero a puntero de entero que contiene una celda la cual es un
entero.
x, y, z son variables de distinto tipo, en las que difieren que unas son enteros y otras son punteros.

x int

y
puntero a int int
z
puntero a puntero a int puntero a int int
Asignaciones en ALGOL
x:=y; copia un entero (2 desreferencing)
x:=z; copia un entero (3 desreferencing)
y:=x; copia un puntero a entero (0 desreferencing)
y:=z; copia un puntero a entero (2 desreferencing)
z:=x; copia un puntero a puntero a entero (MAL)
z:=y; copia un puntero a puntero a entero (0 desreferencing)
- 23 -
En Algol cuando el lado derecho tiene un valor que no satisface las exigencias del lado izquierdo, entonces se hacen
extracciones de valor, la cantidad necesaria.
Extracciones de valor:
 Seguir un puntero (hasta encontrar uno que sirva).
 Extraer valor.
 Desreferencig.

Asignación en C:
x:=y; (2 desreferencing) x=*y;
x:=z; (3 desreferencing) x=**y;
y:=x; (0 desreferencing) y=&x; (& no me da el dato, sino me da una dirección)
y:=z; (2 desreferencing) y=*z;
z:=x; (MAL) MAL
z:=y; (0 desreferencing) z=&y;

En C existe una extracción de valor implícita fija, si se necesitan más hay que escribirlas, por eso para el
desreferencing se debe hacer *y. Cuando no hay desreferencing en C se utiliza & que se suele denominar extracto de
desreferencing.
En C las variables también son direcciones como en Algol.
Si quiero colocar en int un 4 para y =>

y puntero a int int 4

En Algol sería: (ref int)y:=4; En C sería: (*y=4);

Lo mismo si quiero poner un 4 en z =>

z puntero a puntero a int puntero a int int 4

En Algol sería: (ref ref int)z:=4; En C sería: (**z=4);

Casting en Algol: En Algol quiere decir seguir un puntero del lado izquierdo de una asignación.
Int x;
ref int c;
Casting: es un puntero y referencia a la primer celda.
c:=2;
(ref int) y:=x;

Casting en C: Cambiar el tipo de la variable en la instrucción.


Ejemplo 1:
Char z[20];
(int *) & z[3]; // ve al puntero a carácter en esta instrucción como puntero a entero.

Ejemplo 2:
float a;
*((char *)(&a) + 3)=’z’

‘z’

&a bytes

Control de precisión
Aritméticas para representar números. Que utilizan los diferentes lenguajes para representar variables reales. Hay dos
aritméticas básicas (BCD y base 2 (IEEE)).

float a=0.0
for ( ; a!=1.0; a=a+0.1) ¿Cuántas veces imprime a?
{ printf (“%f”,a); }
Cuando compilo con bcdlib (BCD) imprime “a” 10 veces y con stdlib (IEEE - Base2) +∞.
- 24 -
float a=0.1
if (a*10.0==1.0)
printf (“que bien”);
else ¿Qué imprime qué bien o que mal?
printf (“que mal”);

Estas respuestas dependen de con que se compiló el programa:


Si fue con bcdlib (BCD) (que bien). Porque el número que está almacenado en la variable “a” tiene que poder
representarse con 1/10.
Si fue con stdlib (IEEE- Base2) (que mal). Porque el número que está almacenado en la variable “a” tiene que poder
representarse con 1/21, 1/22, 1/2n.

Base 10 (aritmética BCD) BCD = Binario Codificación Decimal (bcdlib)

Ejemplo: 1212.33

1 2 1 2 3 3

4 bits => 16 combinaciones (solo utiliza del 0 al 9. Las A, B, C, D, E y F no).


O sea que 8 + 4 da un número prohibido.
Desventajas:
 Ocupa mucho espacio.
 Mas costosas
 Mas lentas.
 No aprovecha las combinaciones hexadecimales de la “A” a la “F”.
 10+0.1=1

Ventajas:
 10*0.1=1
 Más preciso

decimal binario
6 110
5.5 101.1
5.25 101.01
4.75 100.11
1/10 0.1

Base 2 (IEEE) (stdlib)


Tomando por ejemplo 5.25 =>
La parte entera o sea el 5 lo divido por 2 para obtener el número binario.
La parte real o sea el .25 lo multiplico por 2 para obtener el número binario.

5 1 0.25 *2
2 0 0.50 *2
1 1 1.00 *2 (cuando nos dá 00 => fin)

0.1 es un número periódico.

0.110 -> 0.000110011001100110011….2

Desventajas:
 10*0.1≠1
 No tiene precisión
Ventajas:
 Más económicas.
 Más rápidas.
 Aprovecha mejor el espacio

¿Cómo controlan los diferentes lenguajes la precisión de las variables reales?

- 25 -
Cobol (BCD).
Fortran (Formato propietario IBM, luego IEEE).
Pascal (IEEE).
C (Se elige para todo el programa, Link-editor).
Ada (Se elige para cada variable).
Algol (Se elige para cada variable).

Ejemplo:

Type REALES 10 is new FLOAT delta 0.001


1 El tipo real es 10 y es un tipo real que tiene matemática BCD.
2 Su precisión son 3 decimales.
A:REALES 10;
B:FLOAT;
A+B da error.
Para corregir el error se debe hacer:
A + REALES 10 (B); //Convierte B a tipo REALES 10.
FLOAT (A) + B

¿Cuántas y cuáles son las formas que tienen los lenguajes para controlar la precisión de las variables
reales?
Los lenguajes tienen dos formas de controlar la precisión de las variables reales:
Es lo establecido por la IEEE en la cual se tiene mantiza y exponente para expresar el número. El límite es el tamaño
del tipo real. Algunos lenguajes utilizan BCD para expresar estos números.
Por ejemplo, en ADA se fija la precisión y se utiliza BCD para representarlos.

Conversiones de datos
Conversiones Explícitas: El lenguaje exige que se escriban las conversiones con claridad. Dificulta la escritura, pero
facilita la lectura (mantenibilidad). Se utiliza en ADA.

Conversión Explicita:
REAL X;
INT Z;
X:=REAL(Z);

Conversiones Explícitas en Ada:


type PERAS is NEW FLOAT;
type NARANJAS is NEW FLOAT;
A:PERAS;
B:NARANJAS;
C:FLOAT;
C:=A+B (No se pueden sumar PERAS con NARANJAS, por lo tanto da error)
C:=A+PERAS(B); Error por la asignación
C:=NARANJAS(A)+B;
C:=FLOAT(A+PERAS(B));
C:=FLOAT(NARANJAS(A)+B)); está bien
C:=FLOAT(A)+FLOAT(B)

¿Cómo son las conversiones entre tipos en ADA? ¿Qué consecuencias trae?
Las conversiones en ADA son explícitas. Esto trae la consecuencia que se deben “castear” las variables de diferentes
tipos antes de una asignación. hace que el lenguaje sea más legible y mantenible, aunque hay que escribir más.

Conversiones implícitas: Las conversiones se hacen automáticamente. Se hace una mezcla de tipos. Dificulta la
lectura (mantenibilidad) y facilita la escritura. Se utiliza en C, Algol, Pascal, Fortran.

Conversiones implícitas en C: El compilador hace todo el esfuerzo de acuerdo con la expresión y el tipo de variable
involucrada.
char  short int  int  long int
float  double  long double

Conversiones implícitas en FORTRAN:


COMPLEX A
INTEGER B
DOUBLE PRECISION C
REAL D

- 26 -
COMPLEX DOUBLE

COMPLEX DOUBLE PRECISION

Se transforma Complex
en real Double

B=B*D+A+C REAL

REAL

COMPLEX
INTEGER
COMPLEX DOUBLE

SE REDUCE A ENTERO PARA ASIGNAR A “B”


Se convierte a un nivel superior para poder hacer las operaciones y luego se busca el nivel adecuado para la
asignación final.

Conversiones implícitas en Algol:


 Voiding
 Rowing
 Desreferencing
 Desproceduring
 Uniting
 Widening

Desproceduring: Asigna el valor devuelto por un procedimiento a una variable. Cuando el lado derecho es un
procedimiento y el izquierdo es un tipo numérico => se ejecuta el procedimiento (desproceduring).
cuerpo del procedimiento: instrucciones que lo componen.
Si en Algol escribo: proc xx (---) -----
-----
-----
proc zz (---) -----
-----
-----
proc temp;
temp:=xx;
xx:=zz; dieron vuelta los cuerpos de los procedimientos, en C esto fue parecido a punteros a funciones.
zz:=temp;

El cuerpo del procedimiento xx se copia en el procedimiento temp. Cuando el lado derecho es un procedimiento y el
izquierdo también => se copia el procedimiento.
En Algol los procedimientos se copian.
u:=v (Si u es un número y v es un procedimiento => se debe ejecutar el procedimiento que devuelve el número que
se necesita).

Rowing: Asignar un valor a un arreglo. Si no se especifica posición se almacena a todas las filas.
[4:12] int x; x es un arreglo de enteros.

x:=0; cada componente del arreglo recibe el valor cero, y si x hubiera


tenido algún número => el arreglo recibiría dicho número.

x:=x[6]; para todos los componentes del arreglo adquiere el valor que exista
en la fila número 6.

Widening: Un valor de un tipo, puede ser asignado a una variable de tipo más amplio.

real x; Esto es widening, cuando el lado izquierdo es un real y el derecho es un entero.


int z;
x:=z;

- 27 -
Voiding: Un valor puede ser asignado a una variable nula, perdiéndose el valor.

void a; Esto es voiding, cuando el lado izquierdo es void y el derecho es un real.


real b;
a:=b;

Desreferencing: Seguir el puntero, el lado derecho se adapta al lado izquierdo.

Uniting: Cambia automáticamente el discrimínate según el tipo de dato a asignar. Algol no permite uniones del lado
derecho.

union (int , character) x;


x := 3; // guarda el valor 3.

Compatibilidad: Para algunos lenguajes dos variables son compatibles cuando el tipo de datos y para otros cuando
tiene la misma estructura. Se usan cuando en una operación pueden participar dos tipos de datos.

En C la estructura de las mismas son iguales, la forma de representación es igual.


Typedef struct clientes Typedef struct producto
{ {
int ncli; int npro;
int deuda; int cant;
}CL1; }PR1;
CL1=PR1; (esto está permitido en C Standard, ya que para las dos estructuras tengo dos int).
Muchos compiladores de C++ realizan la compatibilidad por estructura (casi siempre conversiones implícitas) y por
tamaño (si dos estructuras tienen el mismo tamaño se pueden copiar).

En C
Typedef pepe int;
pepe x;
Int y;
Para C “x” e ”y” son compatibles porque tienen la misma estructura.
Para Pascal y ADA no son compatibles porque pepe <> int.

Pascal usa compatibilidad por nombre.

En Ada la compatibilidad es el nombre, si el nombre del tipo son los mismos, ahí dos variables son compatibles.
LADO : INTEGER
SUMA : FLOAT
SUMA = LADO  no se puede hacer, para ADA son incompatibles.
SUMA = FLOAT(LADO)  se realiza la conversión explicita para poder operar.

Algol tiene compatibilida por estructura, para igual tipo de variable. Uso de conversiones implícitas. Por ejemplo
desreferencing y windening.

¿Qué diferencia existe entre las compatibilidades entre tipos de ADA y Algol?
En ADA la compatibilidad es por nombre, o sea, todas las variables del mismo tipo de datos se pueden usar en
operaciones. Las que tengan igual estructura pero distinto nombre son incompatibles para ADA. Usa conversiones
explicitas.
Algol tiene compatibilidad por estructura. Para igualar tipos de variables y hace uso de conversiones implícitas.
ADA tiene la ventaja de tener buena legibilidad y matenibilidad pero se debe escribir mucho.
Algol mejora la escribilidad pero es difícil de leer y de escribir.

¿Que características tiene que tener un lenguaje de programación para ser confiables?
Legibilidad: Propiedad que dice cuanto fácil o difícil es leer un programa.
Mantenibilidad: Propiedad que dice cuanto fácil o difícil es mantener un programa.
Escribilidad: Propiedad que dice cuanto fácil o difícil es escribir un programa.
Legibilidad con mantenibilidad se llevan bien y legibilidad con escribilidad, enemigas.

Instrucción en APL:

+/ρ100 (suma de 1 a 100)


100
-/ρ100 (es Σ (-i)i x i)
i=1

- 28 -
Dificultades con los punteros
Los primeros punteros tenían los siguientes inconvenientes:
En PL1 =>
DECLARE P POINTER;
DECLARE X FIXED; Problemas con el tipo de dato de la celda apuntada
ALLOCATE X SET P;
Declara “X” como un entero, aloja memoria para “X” y hace que apunte a “X”.

1) En PL1 no sabemos a que tipo de dato apuntan los punteros. Esto trae problema con el tipo de dato de la celda
apuntada. Se puede estar usando mal el dato que se apunta.

2) El segundo problema es lo que se denomina punteros colgados o dangling reference que surge cuando un puntero
apunta a una celda de memoria cuyo contenido no se puede garantizar.

Regla de alcance de Algol: En toda asignación en la que se copian direcciones el alcance del lado izquierdo debe ser
≤ que el alcance del lado derecho. El apuntador no puede vivir menos el apuntado, sino lo mismo. El apuntador no
puede sobrevivir apuntando a algo que no existe(regla de alcance). Esto previene el dangling pointer o dangling
referente.
Algol prohíbe que halla punteros colgados.
Ejemplo 1
begin
ref int rx, int x;
begin
ref int ry, int y;
rx:=x; //Mismo alcance y tiempo de vida.
rx:=y; //ERROR cuando muere la función muere la variable “y” y “ry” apunta a algo que no existe.
ry:=x; //El alcance de “ry” es mas reducido que el alcance de “x” que desaparece primero.
ry:=y; //Mismo alcance y tiempo de vida.
end

rx  apunta a algo que no existe


end.
Ejemplo 2
Begin
int a;
ref int p;
Begin
int b;
ref int q;
p:=a; //copia de direcciones donde el lado izq y el der forman el mismo ámbito.
p:=b; //Dangling reference. Asignación prohibida ya que b desaparece 1° que p.
q:=a; //El alcance de q es más reducido que el alcance de a que desaparece 1°.
q:=b; //copia de direcciones donde el lado izq y el der forman el mismo ámbito.
End
End
¿Como se puede producir punteros colgados por problamas de Alcance?
Se originan cuando el alcance de la variable del lado izq de la asignación es mayor al alcance del lado derecho, ya que
puede darse el caso que el puntero, viva mas que lo apuntado.
¿Cómo lo evita Algol?
Lo evita a través de la regla de alcance en la que prohíbe que el alcance de la variable de lado izquierdo sea mayor a
la del lado derecho.

Escribir un ejemplo que refleje como ALGOL evita los punteros colgantes (dailing reference) utilizando su
regla de alcance y describa que pasa en la misma situación en Pascal.

BEGIN ref INT Px, int x;


...
BEGIN ref INT Py; INT y;
Px = x;
Px = y;
Py = x; // Sin problemas
Py = y; // Desaparecen los dos juntos.
END

- 29 -
END

En ALGOL, el alcance del referenciador tiene que ser menor o igual al del referenciado.

Los punteros en PASCAL no dan lugar a tales problemas porque solamente se pueden limitar a UNNAMED :
referenciar anónimos data objects, que son almacenados explícitamente al hacer NEW.
La Instrucción NEW de PASCAL, asigna espacio de memoria para el almacenamiento necesario de una variable
dinámica (ejemplo: una palabra).
A medida que NEW va asignando espacio en el HEAP 1, el espacio disponible se va reduciendo. El puntero que apunta a
la cima del HEAP, va subiendo por las direcciones de memoria, reduciendo cantidad de memoria disponible para otros
valores. Esto puede provocar que la memoria del HEAP se agote, lo que produciría un error al intentar crear una
variable dinámica.

En C:
C, como la mayoría de los lenguajes permite que halla punteros colgados.
Los tres inconvenientes con los punteros son:
1) Se utiliza mal el puntero. Acceso a celda confundiendo el tipo.

2) Puntero colgados:

int *p1, *p2;


p1=(int*)malloc(100);
p2=p1;
free(p2); //si libero p2 => p1 apunta a algo que no existe, este es el caso de puntero colgado.

3) Garbage o Basura: Se fabrica una variable anónima y el puntero desaparece.


Ejemplo 1
int *p1, *p2;
P1=(int*)malloc(100);
P2=(int*)malloc(200);
P1=P2 //se desperdician 100 bytes que apuntaba P2.

Ejemplo 2
p=(char *)malloc(100);
if (a<b) return(); // No libera memoria (Garbage).
else free(p) //Libera memoria (Fragmentación).

Ejemplo 1
int *p;
P = (int*)malloc(100);
P = (int*)malloc(200);

¿El programador puede evitar la generación de dangling pointer?


Puede evitar la generación de dangling pointer ya que se produce por la mala administración de los punteros que crea
el propio programador.

¿Y la fragmentación?
El programador no puede evitar la fragmentación, ya que si bien puede liberar correctamente los punteros, los bloques
de memoria quedaran libres, fragmentando memoria. Es tema del sistema operativo o del lenguaje si administra el
HEAP.
Los lenguajes dinámicos hacen compactación de memoria.

¿El programador puede evitar la generación de garbage en los LTA (Lenguaje Tipo Algol)?
El programador puede evitar la generación de garbage, o sea la generación de bloques en los cuales no hay punteros
que le apunten a él. Esto se puede hacer liberando la memoria cuando la variable puntero va a hacer referencia a otra
variable. Ejemplo:
int *p, *q;
p = (int *) malloc (100);
q = (int *) malloc (200);
free (p);
p =q;
Evitando allocar espacio en memoria para la misma variable.

- 30 -
Administración del Heap

Fragmentación: Con la creación y eliminación de variables dinámicas se producen huecos, que en algún
momento se ocuparán o nó. Estos huecos se producen porque el programador puede cambiar el lugar y el tamaño de
las variables. (Similar a la fragmentación de disco)

Garbage o basura: Zona de memoria inutilizable por el programa, para crear otras variables. La basura se encuentra
en el Heap. Es sólo propiedad de los lenguajes tipo Algol.

En los lenguajes estáticos no es posible que exista garbage porque tiene almacenamiento estático. Estos lenguajes
garantizan que los requerimientos de memoria antes del comienzo de la ejecución del programa. Por lo tanto, toda la
memoria necesaria puede ser ubicada antes de la ejecución del programa.

Explicar porque no existe garbage en los lenguajes dinámicos.


Un lenguaje dinámico todos los datos están referenciados por un símbolo en la tabla de símbolos. Toda zona de
memoria que no está referenciada por un símbolo en la tabla de símbolo se consideran libres para reutilizarlos por la
tabla de símbolos, por lo tanto no existe garbage.
Lo que se comporta como garbage en los lenguajes dinámicos es la fragmentación producida por la asignación no
contigua de memoria.

Lenguajes dinámicos: Lenguajes dinámicos  todas las variables  accedidas desde la tabla de símbolos.

Intérprete

Programa
Cuando la memoria llena está fragmentada se comporta como el
garbage.
Tabla de símbolos Necesito poner los espacios ocupados de memoria en un mismo
lugar, uno a continuación del otro para generar más espacio en
memoria (compactación de memoria). Defragmentación.

Heap Punteros

En lenguajes dinámicos la organización de memoria es:

Interprete Tabla de símbolos Heap

Defragmentación (compactar la memoria): Se recorre la tabla de símbolos buscando el puntero que tenga la
dirección más alta y corro los datos hasta al final, cambio el puntero (en la tabla de símbolos) apuntando al lugar en
donde quedó el dato. Así sucesivamente, compactando los datos en un extremo y en el otro los espacios libres de
memoria.

- 31 -
Garbage collection: Se utiliza para resolver el garbage o basura. Las variables están dispersas en el Heap. Los
punteros los administra el lenguaje, no el usuario. Hay porciones de memorias no usadas.
En memoria virtual el garbage collection casi no ocurre.
En memoria real el garbage collection ocurre frecuentemente.

Lenguajes tipo Algol: Lenguajes tipo Algol  variables dinámicas  accedidas desde la pila de registros de
Activación.

Ejecutable

Pila

Garbage: El lenguaje y el programador no saben donde está


Heap

Hay un riesgo de que la memoria destinada al Heap se termine.


Hay garbage + fragmentación.

En lenguajes tipo Algol la organización de la memoria es:

U
Heap
Ejecutable Pila R.A.

A B C D L4 U5 L3 U4 G2 L2 G1 U3 U2 U1 L1
L

En el Heap hay 3 grupos de cosas:

 Zonas libres (L). Dinámicos


 Zonas usadas (U).
 Garbege (G).

¿Cómo se hace el garbage collection?


El lenguaje fabrica dos listas para administrar el Heap, lista de bloques usados y lista de bloques libres.
Un bloque inaccesible (G1 y G2) es un miembro de la cadena de usados al que no se le puede acceder desde la pila.
1. Marca todos los bloques que considere que son usados.
2. Se recorre la pila. Se buscan todos los punteros que existen en la pila y para cada uno de ellos se le borra la
marca, a los bloques ellos referencian (se borran las marcas de los bloques que se pueden acceder).
3. Cuando se recorrió toda la pila, al bloque que le quedó la marca, ese es basura (G).

El garbage collection se hace cuando necesito memoria, se crea un árbol, pero para crear el árbol necesito memoria.
Veremos un algoritmo de recorrido del árbol utilizando prácticamente nada de memoria (recorrido recursivo):
Cada vez que se transita un puntero, antes de irme pongo un puntero al padre, cada vez que avanzo pongo un
puntero al padre, cuando llego a la hoja retrocede teniendo todo el camino.
Siempre voy a tener un lugar para poder volver. Es como poner una marca para saber por donde pasé.

- 32 -
Raíz

U3

U4 U2

U1

Luego viene la desfragmentación: Se borra el primer usado en la cabecera de usado y se lleva al final de la
memoria. Luego se busca todos los punteros que apuntan a este puntero. Luego se repite la operación.
Hay una estrategia para disminuir el costo de este proceso que se denomina estrategia de contador referencia.
Cada objeto tiene un contador de referencia.
Cada puntero sabe cuantas variables está apuntando.
Cada variable sabe los punteros que la están apuntando.

Ejemplo:
p y q son 2 punteros.

p=q;
resto uno sumo uno

Cuando un bloque tiene su contador de referencia en cero, se borra impidiendo la necesidad de un algoritmo
complicado.

R1

1 1

Cuando se crea una estructura tipo anillo se utiliza este algoritmo.

- 33 -
Contador de referencia

Con malloc (200) lo que hago es crear un espacio de


memoria de 200 bytes que es asignado al puntero q,
q=malloc (200); 1 200 entonces su contador de referencia se pone
automáticamente en 1 ya que solo es apuntado por q
p=q; 2 ese espacio de memoria. Pero cuando hago p=q, el
puntero p también apunta a ese espacio de memoria
donde apunta q, por lo tanto el contador de referencia
de q pasa a 2 ya que son dos los punteros apuntando
a dicho espacio.
Otro ejemplo:
p=malloc (100); 0 Cuando p deja de apuntar a su espacio de memoria y
apunta al de q, entonces se desreferencia y se
1 100 decrementa el contador de referencia de p (que es 0).
q=malloc (200); Y por el contrario ya que al espacio de memoria de q
1 200 apuntan 2 punteros (p y q), su contador de referencia
p=q; 2 es 2.
2
Cuando hay un bloque en el heap cuyo contador de referencia es igual a 0, ese pedazo de memoria
está libre.

Indique un ejemplo de lenguajes para cada una de las siguientes combinaciones.


a) Lenguaje sin garbage collector y sin defragmentación.  C o Pascal. El RUNTIME PACKAGE no se
involucra. Y el garbage collector lo hace el
programador.
b) Lenguaje con garbage collector y sin defragmentación.  Algol.
c) Lenguaje sin garbage collector y con defragmentación.  Lenguajes dinámicos.
d) Lenguaje con ambos.  No hay.
¿Qué es la lista de espacios libres y que problema se le ocurre que puede surgir en un programa que
genera punteros colgantes?
La lista de espacios libres es una secuencia de punteros en el HEAP de porciones de memoria libre (no utilizada),
cuando hay punteros colgantes, en la pila algún descriptor puede estar apuntando a un espacio de memoria que
pertenezca a la lista de libres.
¿Qué se le ocurre que puede ser más peligroso, permitir punteros colgantes o generación de basura?
Generación de basura, porque eso espacios de memoria usados, inaccesibles hacen que el HEAP crezca y se choque
con la pila. Pero tiene la ventaja que algunos lenguajes tienen algoritmos de recuperación (Garbage colection,
contador de referencio).
Punteros colgante porque no puedo acceder a la información que preciso, porque el espacio de memoria que la
guardaba fue liberado y si es asignado nuevamente a otro puntero puedo obtener información errónea.
Suponga que la administración del heap del lenguaje del siguiente ejemplo se basa en la existencia de
contadores de referencia.
¿Cuáles de las siguientes asignaciones afectan dichos contadores y de qué manera?
¿Dónde están ubicados los mismos?
int a,b;
int *pa, *pb, *pc, *pd;
pa=&a;
pb=&b;
pc=(int*)malloc(sizeof(int));
pd=(int*)malloc(sizeof(int));
pa=pc;
pd=pb;
Contador de referencia: En el algoritmo de conteo de referencia cuando hago malloc o free este algoritmo, reserva
un pedazo en el heap (celda) que es el contador de referencia.
pc=(int*)malloc(sizeof(int));  pc=1
pd=(int*)malloc(sizeof(int));  pd=1
pa=pc;  pc=2
pd=pb;  pd=0

¿Cuáles son las condiciones para que un elemento del n heap sea devuelto a la cadena de libre utilizando
contador de referencia?
En el algoritmo de conteo de referencia cuando hago “malloc” o “free” este algoritmo reserva un pedazo en el heap
(celda), que es el contador de referencia. Cuando hay un puntero en el heap cuyo contador de referencia es igual a
cero, ese pedazo de memoria está libre.

- 34 -
Parsing Ascendente (LR o SLR)

Se basa esencialmente en la operación de reducción, en reemplazar el lado derecho por el lado izquierdo
de las reglas. Durante el desarrollo del parsing ascendente trabajaremos con la gramática a continuación
descripta, la hacemos recursiva a derecha y luego la factorizamos para ver cual es la acción que nos dice
el terminal que hay que hacer. El PA no requiere modificaciones esenciales.

1) E E+T
2) E T
3) T T*F
4) T F
5) F id
6) F cte
7) F (E)
E(Expresión), T (Término), F (Factor)

Algoritmo de PA:

Hay que empezar marcando el Estado de Avance del Reconocimiento de una Regla (llamado Item), se
expresa con un punto.

Ej:
T .T*F No se ha reconocido nada de la regla.
T T.*F Se ha reconocido parcialmente.
T T*.F Se ha reconocido parcialmente.
T T*F. Se ha reconocido toda la regla.

Primer Paso del PA:

a) El PA exige que la gramática tenga una sola definición. Si no se cumple esa condición se agrega una
regla nueva donde se cambia la Hipótesis y se redefine la Hipótesis en base a la regla vieja. Un ejemplo de
definición duplicada es el caso de E.

0) E´ E
1) E E+T
2) E T
3) T T*F
4) T F
5) F id
6) F cte
7) F (E)

- 35 -
b) Luego se construyen grupos de ítems:

Grupo Cero:

 El primer grupo se construye poniendo el punto delante del lado derecho de las reglas.
 Luego, si el punto quedó delante de un No Terminal se escribe su definición.
 Para finalizar agregamos la palabra recursivamente.

Como el punto quedó


delante de T, E y F
(No Terminales), escribo sus definiciones.
0) E´ . E
E . E+T Definición de E
E . T
T . T*F
T . F Definición de T
F . id
F . cte Definición de F
F . (E)

Grupo Uno: [0, E]


Si en el grupo 0 encuentro una E

1) E´ E.
E E . +T

Grupo Dos: [0, T], Luego de 9 [0,6 y T],


Si en el grupo 0 encuentro una T

2) E T.
T T . *F

Grupo Tres: [0, F], Luego de G9 [0,6 y F], G10 [0,6,7 y F], G11 [0,6,7,8 y F],
Si en el grupo 0 encuentro una F

3) T F.

- 36 -
Grupo Cuatro: [0, id], Luego de G9 [0,6 y id], G10 [0,6,7 y id], G11 [0,6,7,8 y id],
Si en el grupo 0 encuentro una id.

4) F id .

Grupo Cinco: [0, cte], Luego de G9 [0,6 y cte], G10 [0,6,7 y cte], G11 [0,6,7,8 y cte],
Si en el grupo 0 encuentro una cte.

5) F cte .

Grupo Seis: [0, (], Luego de G9 [0,6 y (], G10 [0,6,7 y (],G11 [0,6,7,8 y (],
Si en el grupo 0 encuentro un (.

6) F ( . E) Punto delante de un NT, debo escribir toda su definición.


E . E+T
E .T
T . T*F
T .F Definición de E
F . id
F . cte
F . (E)

Con el grupo 0 ya no puedo encontrar mas nada entonces empiezo con el 1.

Grupo Siete: [1, +], Luego de G12 [1,9 y +]


En el grupo 1 solo puedo encontrar un +.

7) E E+ . T Punto delante de un NT, debo escribir toda su definición.


T . T*F
T .F
F . id Definición de T
F . cte
F . (E)

Con el grupo 1 ya no puedo encontrar mas nada entonces empiezo con el 2.

Grupo Ocho: [2, *], Luego de G12 [2,10 y *]


En el grupo 2 encuentro un *.

8) T T* . F Punto delante de un NT, debo escribir toda su definición.


F . id
F . cte Definición de F
F . (E)

- 37 -
Con el grupo 2, 3, 4 y 5 ya no puedo encontrar mas nada entonces empiezo con el 6.

Grupo Nueve: [6, E]


En el grupo 6 encuentro una E.

9) F (E . )
E E . +T

Con el grupo 6 ya no puedo encontrar mas nada tal que, las reglas que se descubren luego ya lo fueron en
los grupos 2,3,4,5 y 6, ese dato se encola (Color ciruela).

Grupo Diez: [7, T]


Luego empiezo con el 7, en el grupo 7 encuentro una T.

10) E E +T .
T T . *F

Con el grupo 7 ya no puedo encontrar mas nada tal que, las reglas que se descubren luego ya lo fueron en
los grupos 3,4,5 y 6, ese dato se encola (Color anaranjado).

Grupo Once: [8, F]


Luego empiezo con el 8, en el grupo 8 encuentro una F.

11) T T*F .

Con el grupo 8 ya no puedo encontrar mas nada tal que, las reglas que se descubren luego ya lo fueron en
los grupos 4,5 y 6, ese dato se encola (Color Fucsia).

Grupo Doce: [9, )]


Luego empiezo con el 9, en el grupo 9 encuentro una ).

12) F (E) .

El otro estado que aparece en el Grupo Nueve ya está definido en el Grupo Siete, entonces encola (color
gris).

La última regla que falta estudiar es la segunda del Grupo Diez cuyo estado ya está definido en el Grupo
Ocho, ese dato se encola (Color Violeta)

------------------------------------FIN DEL PASO UNO---------------------------------------

- 38 -
Segundo Paso del PA:

Construir una matriz de Terminales y NT como columnas y los grupos del paso 1 como filas. Una vez
construido esto lo veo como un autómata llamando estados a los grupos.

Pasos para completar la matriz:

I ) Se marcan todos los pasajes de un grupo a otro producidos por un NT.


II ) En la intersección de los pares seleccionados en I) Enumero en la matriz de la siguiente forma: (Paso
los datos de los colas de NT a la matriz)

En la intersección de 0 y E escribo un 1
En la intersección de 0, 6 y T escribo un 2
En la intersección de 0, 6, 7 y F escribo un 3
En la intersección de 6 y E escribo un 9
En la intersección de 7 y T escribo un 10
En la intersección de 8 y F escribo un 11

En la intersección de Grupo desde y el No terminal, escribo el Número del Grupo al cual llego.

III ) Ahora voy a buscar todos los Grupos a los cuales llegué por medio de un Terminal y los voy a
identificar con una letra: (Paso los datos de los colas de T a la matriz)

En la intersección de 0, 6, 7, 8 y id escribo d4
En la intersección de 0, 6, 7, 8 y cte escribo d5
En la intersección de 0, 6, 7, 8 y ( escribo d6
En la intersección de 1, 9 y + escribo d7
En la intersección de 2, 10 y * escribo d8
En la intersección de 9 y ) escribo d12

En la intersección de Grupo desde y el Terminal, escribo el Número del Grupo al que llegué por medio de
la regla.

IV ) Ahora hay que mirar el conjunto de reglas que tienen el punto al final (ya definidas). El conjunto de
Reglas es 7 + 1 originalmente.
Si hubiera alguna duplicación voy a tener un conflicto (Reduce), del mismo modo si un grupo tuviera dos
reglas.
Si se produce alguna de ellas sucede que este algoritmo no puede compilar dicha gramática.
En el ejemplo puede verificarse que:

El grupo 0 produce E´ (Grupo 1, Regla 0)


El grupo 2 produce E (Grupo 2, Regla 2)
El grupo 4 produce T (Grupo 3, Regla 4)
El grupo 5 produce F (Grupo 4, Regla 5) OK, están todas la reglas
El grupo 6 produce F (Grupo 5, Regla 6)
El grupo 7 produce E (Grupo 10, Regla 1)
El grupo 3 produce T (Grupo 11, Regla 3)
El grupo 7 produce F (Grupo 12, Regla 7)

- 39 -
V ) Ahora debo hacer los reemplazos que mandan las reglas, los reemplazos válidos dependen de que el
carácter que sigue esté definido en las mismas 7 reglas.
Entonces vamos a construir los conjuntos de los posibles caracteres siguientes de los NT: E´, E, T y
F.

( E´) = {$}
( E ) = {+, ), $}
( T ) = {*, +, ), $}
( F ) = {*, +, ), $}

Si T se transforma en E, entonces, todo lo que le puede seguir a E le puede seguir a T..


Luego se marcan las reducciones permitidas (columnas no utilizadas) por cada fila seleccionada en el paso
IV) con las columnas de los posibles caracteres siguientes, y en la reducción de la cero voy a escribir FIN.

Id Cte * + ( ) $ E T F
0 D4 D5 D6 1 2 3
1 D7 FIN
2 D8 R2 R2 R2
3 R4 R4 R4 R4
4 R5 R5 R5 R5
5 R6 R6 R6 R6
6 D4 D5 D6 9 2 3
7 D4 D5 D6 10 3
8 D4 D5 D6 11
9 D7 D12
10 D8 R1 R1 R1
11 R3 R3 R3 R3
12 R7 R7 R7 R7
D: desplazamiento; R: Reducción; 1..11 son los grupos del PASO 1

Todo esto lo realiza automáticamente el YACC, manualmente, en la práctica para con los lenguajes reales
sería imposible. YACC produce esta matriz y construye un programa que hace funcionar el autómata.

- 40 -
Ejemplo de cómo ejecuta el compilador el Parsing Ascendente.
((id)) según la gramática antes descripta sería:
E

( ( id ) )
Ahora veremos como se van aplicando las reglas de la matriz: (( ID ))

ESTADO CARACTER PILA COMENTARIO


LEIDO
0 (
(, 0 Cuando aparece una operación de desplazamiento, el
carácter y el estado lo manda a la pila.
6 (
(, 0, ( ,6 6 y id generan desplazamiento al estado 4.
6 ID
4 ) (, 0, ( ,6, ID, 6 4 y ) en la matriz es reducción por regla 5, la regla 5
dice que id define a F. Cuando se produce una
reducción el carácter leído “)” permanece inalterado.

(, 0, ( ,6, F, 6 Luego, F y 6 en la matriz me mandan al estado 3, 3 y


) en la matriz es reducción por regla 4, la regla 4 dice
3 )
que F define a T.
2 ) (, 0, ( ,6, T, 6 Luego, T y 6 en la matriz me mandan al estado 2, 2 y
) en la matriz es reducción por regla 2, la regla 2 dice
que T define a E.
9 ) (, 0, ( ,6, E, 6 E y 6 me mandan al estado 9 y generan
desplazamiento a la pila.
12 ) (, 0, ( ,6, E, 6, ), 9 9 y ) me mandan al d12 y se cargan en la pila. 12 y )
en la matriz es reducción por la regla 7, la regla 7 dice
que ( E ) define a F. El primer 6 ,después del carácter
mas izquierdo reemplazado “(”, es el único que queda.
3 ) (, 0, F , 6 Repiten pasos ya descriptos
2 ) (, 0, T , 6 Repiten pasos ya descriptos
9 ) (, 0, E , 6 Repiten pasos ya descriptos
12 $ (, 0, E , 6, ), 9 La 12 manda desplazamiento pero no hay nada mas
para leer, entonces miro lo que queda en la pila. 12 y
$ es reducción por la 7, nuevamente ( E ) define a F.
3 $ F, 0 F y 0 van a la 3. La 3 y $ van a R4. F define T.
2 $ T, 0 T y 0 van a la 2. La 2 y $ van a R2. T define E.
1 $ E, 0 E y 0 van a la 1. La 1 y $ van a FIN.
FIN

- 41 -
3 E

2 T

9 F

3 T

2 F

9 E

3 T

2 F

1 FIN ((id))

Otro ejemplo: cte * ( id + cte )

ESTADO CARACTER LEÍDO PILA


0 Cte
5 * Cte, 0
3 * F, 0
2 * T, 0
8 ( T, 0, *, 2
6 Id T, 0, *, 2, (, 8
4 + T, 0, *, 2, (, 8, id, 6
3 + T, 0, *, 2, (, 8, F, 6
2 + T, 0, *, 2, (, 8, T, 6
9 + T, 0, *, 2, (, 8, E, 6
7 Cte T, 0, *, 2, (, 8, E, 6, +, 9
5 ) T, 0, *, 2, (, 8, E, 6, +, 9, cte, 7
3 ) T, 0, *, 2, (, 8, E, 6, +, 9, F, 7
10 ) T, 0, *, 2, (, 8, E, 6, +, 9, T, 7
9 ) T, 0, *, 2, (, 8, E, 6
12 $ T, 0, *, 2, (, 8, E, 6, ), 9
11 $ T, 0, *, 2, F, 8
2 $ T, 0
1 $ E, 0
FIN

- 42 -
Luego voy a repasar el parsing mirando solamente las reducciones:

5 * R6
3 * R4
4 + R5
3 + R4
2 + R2
5 ) R6
3 ) R4
10 ) R1
12 $ R7
11 $ R3
12 $ R2

6, 4, 5, 4, 2, 6, 4, 1, 7, 3, 2

RESULTADO DEL PARSING: Son la lista de reglas que prueban que el


programa esté bien escrito.

COMPILADORES

Compiladores Un compilador es un programa que lee un programa escrito en un lenguaje, el lenguaje


“Fuente”, y lo traduce a un programa equivalente en otro lenguaje, el lenguaje “objeto”. Lenguajes
fuentes son lenguajes de programación tradicionales como FORTRAN o Pascal, hasta los lenguajes
especializados que han escrito virtualmente en todas las áreas de aplicación de la informática. Los
lenguajes objeto son igualmente variados, puede ser otro lenguaje de programación o el lenguaje de
máquina de cualquier computador entre un microprocesador y un supercomputador.

Análisis Léxico En un compilador el análisis lineal se llama análisis léxico o exploración. Análisis lineal,
en la cadena de caracteres que constituye el programa fuente se lee de izquierda a derecha y se agrupa
en componentes léxicos, que son secuencias de caracteres que tienen un significado colectivo. Un
analizador léxico puede aislar un analizador sintáctico de la representación en lexemas de los
componentes léxicos. Se empieza haciendo una lista de algunas de las funciones para que las realice un
analizador léxico.

Análisis Sintáctico Este implica agrupar los componentes léxicos del programa fuente en frases
gramaticales que el compilador utiliza para sintetizar la salida. Por lo general, las frases gramaticales del
programa fuente se representan mediante un árbol de análisis sintáctico.
Árbol Sintáctico Es una representación compacta del árbol de análisis en el que los operadores aparecen
como los nodos interiores y los operandos de un operador son los hijos de nodo para ser operador.

Análisis Semántico La fase de análisis semántico revisa el programa fuente para tratar de encontrar
errores semánticos y reúne la información sobre los tipos para la fase posterior de generación de código.
En ella se utiliza la estructura jerárquica determinada por la fase de análisis sintáctico para identificar los
operadores y operandos de expresiones y proposiciones.

Tabla de Símbolos Es una estructura de datos que contiene un registro por cada identificador, con los
campos para los atributos del identificador. La estructura de datos permite encontrar rápidamente el
registro de cada identificador y almacenar o consultar rápidamente datos de ese registro.

- 43 -
Detección e Información de Errores Las fases de análisis sintáctico y semántico por lo general
manejan una gran porción de los errores detectables por el compilador. La fase léxica puede detectar
errores donde los caracteres restantes de la entrada no forman ningún componente léxico del lenguaje.
Los errores donde la cadena de componentes léxicos violan las reglas de estructura (sintaxis) del lenguaje
son determinados por la fase de análisis sintáctico. Durante el análisis semántico el compilador intenta
detectar construcciones que tengan la estructura sintáctica correcta, pero que no tengan significado para
la operación implicada, por ejemplo si se intenta sumar dos identificadores, uno de los cuales es el nombre
de una matriz, y el otro, el nombre de un procedimiento.

Optimización de Código La fase de optimización de código trata de mejorar el código intermedio, de


modo que resulte un código de máquinas más rápido de ejecutar. Algunas optimizaciones son triviales.

Generación de Código La fase final de un compilador es la generación de código objeto, que por lo
general consisten en código de máquina relocalizable o código ensamblador. Las posiciones de memoria se
selecciona para cada una de las variables usadas por el programa.

Código Ensamblador Es una versión mnemotécnica del código de máquina, donde se usan nombres en
lugar de códigos binarios para operaciones, y también se usan nombres para las direcciones de memoria.

Gramática Independiente del Contexto Una gramático describe de forma natural la estructura
jerárquica de muchas construcciones de los lenguajes de programación.
Una gramática independiente del contexto tiene 4 componentes:
1) Un conjunto de componentes léxicos, denominados símbolos terminales.
2) Un conjunto de no terminales.
3) Un conjunto de producciones, en el que cada producción consta de un no terminal, llamado lado
izquierdo de la producción, una flecha y una secuencia de componentes léxicos y no terminales, o
ambos, llamado lado derecho de la producción.
4) La denominación de uno de los no terminales como símbolo inicial.

Propiedades de un árbol de análisis sintáctico.


1) La raíz está etiquetada con el símbolo inicial.
2) Cada hoja está etiquetada con un componente léxico.
3) Cada nodo interior está etiquetado con un no terminal.
4) Si ‘A’ es el no terminal que etiqueta a algún nodo interior y X 1, X2, ..., Xn son etiquetas de los hijos
de ese nodo, de izquierda a derecha, entonces A  X1X2...Xn es una producción. Aquí, X1,X2, ..., Xn
representa un símbolo que es un terminal o un no terminal.

Análisis Sintáctico Descendente La construcción descendente de un árbol de análisis sintáctico se hace


empezando por la raíz, etiquetada con el terminal inicial, realizando de forma repetida los dos pasos
siguientes:
1) En el nodo ‘n’, etiquetado con el no terminal ‘A’, selecciónese una de las producciones para ‘A’ y
constrúyase los hijos de ‘n’ para los símbolos del lado derecho de la producción.
2) Encuéntrese el siguiente nodo en el que ha de construirse un subárbol.

Análisis Sintáctico Predictivo. El análisis sintáctico descendente recursivo es un método descendente


en el que se ejecuta un conjunto de procedimientos recursivos para procesar la entrada. A cada no
terminal de una gramática se asocia un procedimiento. Aquí, se considera una forma especial de análisis
sintáctico descendente recursivo, llamado análisis sintáctico predictivo, en el que el símbolo de preanálisis
determina sin ambigüedad el procedimiento seleccionado para cada no terminal, La secuencia de
procedimientos llamados en el procedimientos de la entrada define implícitamente un árbol de análisis
sintáctico para la entrada.

Razones para dividir el análisis léxico y el análisis sintáctico Hay varias razones:
1) Un diseño sencillo es quizás la consideración más importante. Separa el análisis léxico del análisis
sintáctico a menudo permite simplificar una u otra de dichas fases. Por ejemplo, un analizador
sintñactico que incluya las convenciones de los comentarios y espacios en blanco es bastante más
complejo que uno que pueda comprobar si los comentarios y espacio en blanco ya han sido
eliminados de la convenciones léxicas de las sintácticas puede dar origen a un diseño del lenguaje.
- 44 -
2) Se mejora la eficiencia del compilador. Un analizador léxico independiente permite construir un
procesador especializado y potencialmente más eficiente para esta función. Gran parte de tiempo
se consume en leer el programa fuente y dividirlo en componentes léxicos. Con técnicas
especializadas de manejo de buffers para la lectura de caracteres de entrada y procesamiento de
componentes léxicos se puede mejorar significativamente el rendimiento de un compilador.
3) Se mejora la transportabilidad del compilador, las peculiaridades del alfabeto de entrada y otras
anomalías propias de los dispositivos pueden limitarse al analizador léxico. A representación de
símbolo especiales o no estándar, como en Pascal, pueden ser aisladas en el analizador léxico.

Diagramas de Transiciones Como paso intermedio en la construcción de un analizar léxico, primero se


produce un diagrama de flujo estilizado, llamado Diagrama de Transiciones. Estos representan las acciones
que tienen lugar cuando el analizador léxico es llamado por el analizador sintáctico para obtener el
siguiente componente léxico. Se utiliza para localizar la información sobre caracteres que se detectan a
medida que el apuntador delantero examina la entrada, ésto se hace cambiando de posición en el
diagrama según se leen los caracteres.

Autómatas Finitos Un reconocedor de un lenguaje es un programa que toma como entrada una cadena
‘x’ y responde “si” si ‘x’ es una frase del programa, y “no”, si no lo es. Se compila una expresión regular
en un reconocedor construyendo un diagrama de transición generalizado llamado autómata finito. Un
autómata finito puede ser determinista o no determinista, donde “no determinista” significa que en un
estado se puede dar el caso de tener más de una transición para el mismo símbolo de entrada.

Ventajas de la gramática.
1) Una gramática da una especificación sintáctica precisa y fácil de entender de un lenguaje de
programación.
2) A partir de algunas clases de gramáticas se puede construir automáticamente un analizar sintáctico
eficiente que determine si un programa fuente está sintácticamente bien formado. Otra ventaja es que
el proceso de construcción del analizador sintáctico puede revelar ambigüedades sintácticas y otras
construcciones difíciles de analizar que de otro modo podrían pasar sin detectar en la fase inicial de
diseño de un lenguaje y de su compilador.
3) Una gramática diseñada adecuadamente imparte una estructura a un lenguaje de programación útil
para la traducción de programas fuente a código objeto correcto y para la detección de errores.
Existen herramientas para convertir descripciones de traducciones basadas en gramática en programas
operativos.

Tratamientos de Errores Cuando se detecta luego de que se publica para el usuario. ¿Qué pasa luego
que se detecta el error?
1) Pánico, Resincronización. Provoca “ERROR”. Ignora el programa hasta un punto seguro (;) y sigue con
el resto. Basta que suceda uno para que siga compilando entre ‘;’, pero es para que el usuario sepa cual
es el error.
2) Borrado, Inserción y Reemplazo. Genera “WARNINGS”. No suspende la generación de código. Consiste
en tomar lo que hay en programa y cambiarlo. Genera Warnings y sigue generando código. Ej. Cambiar el
tamaño de una variable por una acortada. Borrado: Si viene una nueva línea lo cambia por otra cosa.
3) Gramática de Error. Genera “WARNINGS”. Se agrega un BNF un terminal que sea error y se escribe lo
que sería error (Se definen errores en BNF). Ej. <ERROR>id cte / + - / * +
Inconveniente: 1- La cantidad de errores es muy alta. 2- la ejecución se vuelve muy lenta.
Por lo tanto, solo se utiliza para errores clásicos. Ej. Cuando termina un programa en Pascal.

Clasificación de Errores Si se escribe ‘eles’ en vez de ‘else’ es un error léxico que el compilador no
puede detectar lo agarra el analizador sintáctico. Hay errores en las distintas fases.
1- Léxicos: Casi todos “WARNINGS”. Se los trata con Borrado, inserción y Reemplazo. Son errores
descubiertos en la parte léxica.
2- Sintáctico: son prácticamente ‘error’. La mayor parte son ‘Pánico’ y muy pocos son por ‘Gramática
de error’. Un error sintáctico suspende la generación de código.
3- Generación de código: Son la mayoría ‘Warnings’. Se tratan con Borrado, Inserción y Reemplazo. Son
conocidos con errores de la Tabla de Símbolos (variables no declaradas) o declaradas dos veces, o tipo
incompatibles. Algunos aparecen en la optimización (suma de constantes fuera del límite).

- 45 -
Qué hago cuando descubrí un error? Qué hace el compilador después de un error?

1. Pánico.
a. No se dónde estoy parado, hasta que no aparezca algo (generalmente: “;”). A partir de
eso, no se compila más.
b. No genero Código.
c. Se usa en Parsing (casi siempre). Pocos casos, con gramática de error.

2. Estrategias de insersión, borrado y reemplazo.


a. Insersión: Agregar algo para que el programa este bien.
b. Borrado: Borrar algo para que el programa este bien.
c. Reemplazar: Cambiar algo para que el programa este bien.

Conserva generación de código  para usar warning (cambio programa).


Se usa en el analizador léxico y en la generación de código.

3. Estrategias de gramática de error.


a. Tener previsto un error (gramática prevista para errores frecuentes). Problema más
sentencias de error que validan.

COMPILADORES

Un compilador es un programa que lee un programa escrito en un lenguaje, el lenguaje fuente, y lo


traduce a un programa equivalente en otro lenguaje, el lenguaje objeto. Como parte importante de este
proceso de traducción, el compilador informa a su usuario de la presencia de errores en el programa
fuente.

Programa compilador Programa


fuente objeto

Mensajes de
error

- 46 -
Las fases de un compilador

Programa fuente

Analizador léxico

Analizador sintáctico

Administrador de la Generador de código Manejador de errores


tabla de símbolos intermedio

Optimizador de código

Generador de código

Programa objeto

Analizador léxico

El analizador léxico es la primera fase de un compilador. Su principal función consiste en leer los
caracteres de entrada y elaborar como salida una secuencia de componentes léxicos que utiliza el
analizador sintáctico para hacer el análisis. Esta interacción suele aplicarse convirtiendo al analizador
léxico en una subrutina del analizador sintáctico. Recibida la orden “obtener el siguiente componente
léxico” del analizador sintáctico, el analizador léxico lee los caracteres de entrada hasta que pueda
identificar el siguiente componente léxico.
Cuando el analizador léxico es la parte del compilador que lee el texto fuente, también puede realizar
ciertas funciones secundarias en la interfaz del usuario, como eliminar del programa fuente comentarios y
espacios en blanco en forma de caracteres de espacio en blanco, caracteres TAB y de línea nueva. Otra
función es relacionar los mensajes de error del compilador con el programa fuente.
token
Programa Analizador Analizador
fuente léxico sintáctico
Obtén el siguiente token

Tabla de
símbolos

Aspectos del análisis léxico: Hay varias razones para dividir la fase de análisis de la compilación en
análisis léxico y análisis sintáctico.
1_ Un diseño sencillo es quizá la consideración más importante. Separar el análisis léxico del análisis
sintáctico a menudo permite simplificar una u otra de dichas fases.
2_ Se mejora la eficiencia del compilador.
3_ Se mejora la transportabilidad del compilador.

Componentes léxicos (token), patrones y lexemas: Cuando se menciona el análisis sintáctico, los
términos “componentes léxicos (token)”, “patrón”, “lexema” se emplean con significados específicos. En
general, hay un conjunto de cadenas en la entrada para el cual se produce como salida el mismo token.
- 47 -
Este conjunto de cadenas se describe mediante una regla llamada patrón asociado al token. Se dice que el
patrón concuerda con cada cadena del conjunto. Un lexema es una secuencia de caracteres en el
programa fuente con la que concuerda el patrón para un componente léxico.

Token Lexemas de ejemplo Descripción informal del patrón


const const const
if if if
relación <, <=, =, <>, >, >= < o <= o = o <> o >= o >
id pi, cuenta, D2 letra seguida de letras y dígitos
num 3.1416, 0, 6.02E23 cualquier constante numérica
literal “vaciado de memoria” cualquier carácter entre “ y ”, excepto “
Ejemplos de componentes léxicos

Los componentes léxicos se tratan como símbolos terminales de la gramática del lenguaje fuente, con
nombres en negritas para representarlos. Los lexemas para el componente léxico que concuerdan con el
patrón representan cadenas de caracteres en el programa fuente que se pueden tratar juntos como una
unidad léxica.
En la mayoría de los lenguajes de programación, se consideran token las siguientes construcciones:
palabras clave, operadores, identificadores, constantes, cadenas literales y signos de puntuación,
paréntesis, punto y coma. Cuando en la secuencia de caracteres aparece pi en el programa fuente, se
devuelve al analizador sintáctico un componente léxico que representa un identificador. La devolución de
un componente léxico a menudo se realiza mediante el paso de un número entero correspondiente al
componente léxico. Este entero es al que hace referencia el id en negritas en la tabla.

Atributos de los componentes léxicos: Cuando concuerda con un lexema más de un patrón, el
analizador léxico debe proporcionar información adicional sobre el lexema concreto que concordó con las
siguientes fases del compilador. Por ejemplo, el patrón num concuerda con las cadenas 0 y 1, pero es
indispensable que el generador de código conozca que cadena fue realmente la que se emparejó.
El analizador léxico recoge información sobre los componentes léxicos en sus atributos asociados. Los
componentes léxicos influyen en las decisiones del análisis sintáctico, y los atributos, en la traducción de
los componentes léxicos. En la práctica, los componentes léxicos suelen tener un solo atributo –un
apuntador a la entrada de la tabla de símbolos donde se guarda la información sobre el componente
léxico; el apuntador se convierte en el atributo del componente léxico. A efectos de diagnóstico, puede
considerarse tanto el lexema para un identificador como el número de línea en el que éste se encontró por
primera vez. Estos dos elementos de información se pueden almacenar en la entrada de la tabla de
símbolos para el identificador.

Errores léxicos: Son pocos los errores que se pueden detectar simplemente en el nivel léxico porque un
analizador léxico tiene una visión muy restringida de un programa fuente. Si aparece la cadena fi por
primera vez en un programa en C en el contexto:
fi (a==f(x))...
un analizador léxico no puede distinguir si fi es un error de escritura de la palabra clave if o si es un
identificador de función no declarado. Como fi es un identificador válido, el analizador léxico debe devolver
el componente léxico de un identificador y dejar que alguna otra frase del compilador se ocupe de los
errores.
Pero supóngase que surge una situación en la que el analizador léxico no puede continuar porque ninguno
de los patrones concuerda con un prefijo de la entrada restante. Tal vez la estrategia de recuperación más
sencilla sea la recuperación en “modo de pánico”. Se borran caracteres sucesivos de la entrada restante
hasta que el analizador léxico pueda encontrar un componente léxico bien formado. Esta técnica de
recuperación puede confundir en ocasiones al analizador sintáctico, pero en un ambiente de computación
interactivo puede resultar bastante adecuada.
Otras posibles acciones de recuperación de errores son:
1. borrar un carácter extraño.
2. insertar un carácter que falta.
3. reemplazar un carácter incorrecto por otro correcto.

- 48 -
4. intercambiar dos caracteres adyacentes.
Eliminación de espacios en blanco y comentarios: Muchos lenguajes permiten que aparezcan
“espacios en blanco” (caracteres en blanco, caracteres TAB y de nueva línea) entre los componentes
léxicos. Los comentarios también pueden no ser considerados por el analizador sintáctico y el traductor,
por tanto también se pueden tratar como espacios en blanco.
Si el analizador léxico elimina los espacios en blanco, el analizador sintáctico nunca tendrá que
considerarlos. La alternativa de modificar la gramática para incorporar los espacios en blanco dentro de la
situación no es tan fácil de implementar.

Constantes: En cualquier momento que aparece un dígito solo en una expresión, parece razonable poner
una constante entera arbitraria en su lugar. Como una constante entera es una secuencia de dígitos,
pueden admitirse constantes enteras añadiendo producciones a la gramática de las expresiones o creando
token para tales constantes. La tarea de agrupar dígitos para formar enteros se le asigna, por lo general, a
un analizador léxico, porque los números se pueden tratar como unidades simples durante la traducción.
Sea num el token que representa un entero. Cuando una secuencia de dígitos aparece en la cadena de
entrada, el analizador léxico pasará num al analizador sintáctico. El valor del entero se pasará como
atributo del token num. Lógicamente, el analizador léxico pasa el token y el atributo al analizador
sintáctico. Al escribir un token y su atributo como una tupla encerrada entre < >, la entrada

31+28+59

se transforma en la secuencia de tuplas

<num, 31> <+,> <num, 28> <+,> <num, 59>

el token + no tiene atributos. Los segundos componentes de las tuplas, los atributos, no desempeñan
papel alguno durante el análisis sintáctico, pero son necesarios en la traducción.

Reconocimiento de identificadores y palabras claves: Los lenguajes utilizan identificadores como


nombres de variables, matrices, funciones y similares. A menudo, una gramática para un lenguaje trata a
un identificador como un token. Un analizador basado en esta gramática espera ver el mismo token, por
ejemplo, id, cada vez que un identificador aparezca en la entrada. Por ejemplo, la entrada:

cuenta = cuenta + incremento;

sería convertida por el analizador léxico en la cadena de token

id = id + id;

Esta cadena de token se utiliza en el analizador sintáctico.


Cuando se considera el análisis léxico de (cuenta = cuenta + incremento;), es útil distinguir entre el
componente léxico id y los lexemas cuenta e incremento asociados con los casos de este token. El
traductor necesita saber que el lexema cuenta forma los dos primeros casos de id en (id = id + id;) y que
el lexema incremento forma el tercer caso de id.
Cuando en la entrada aparece un lexema que forma un identificador, se necesita algún mecanismo para
determinar si el lexema apareció antes. Para tal mecanismo se usa una tabla de símbolos. El lexema se
almacena en la tabla de símbolos y un apuntador a esa entrada de la tabla de símbolos se convierte en un
atributo del componente léxico id.
Muchos lenguajes utilizan cadenas de caracteres fijas, como bejín, end, if y otras más, como signos de
puntuación o para identificar ciertas construcciones. Estas cadenas de caracteres, llamadas palabras clave,
suelen satisfacer las reglas para formar identificadores, por lo que se necesita un mecanismo para decidir
cuándo un lexema forma una palabra clave y cuándo forma un identificador. El problema resulta más fácil
de resolver si las palabras clave son reservadas, es decir, si no se pueden usar como identificadores.
Entonces, una cadena de caracteres forma un identificador sólo si no es una palabra clave.
Si el lenguaje no convierte en reservadas las palabras clave, entonces es indispensable que las palabras
clave se introduzcan en la tabla de símbolos advirtiendo su posible uso como palabras clave.
El problema del asilamiento de los token también surge si aparecen los mismos caracteres en los lexemas
de más de un token, como en <, <= y <> en Pascal.

- 49 -
Analizador sintáctico

El analizador sintáctico obtiene una cadena de componentes léxicos del analizador léxico, y comprueba
que la cadena pueda ser generada por la gramática del lenguaje fuente. Se supone que el analizador
sintáctico informará de cualquier error de sintaxis de manera inteligible. También debería recuperarse de
los errores que ocurren frecuentemente para poder continuar procesando el resto de su entrada.

token
Programa Analizador Analizador Resto de la
fuente léxico sintáctico Árbol de etapa inicial Representación
Obtén el análisis intermedia
siguiente token sintáctico

Tabla de
símbolos

Manejo de errores sintácticos: Si un compilador tuviera que procesar sólo programas correctos, su
diseño e implementación se simplificarían mucho. Pero los programadores a menudo escriben programas
incorrectos, y un buen compilador debería ayudar al programador a identificar y localizar errores.
Considerar desde el principio el manejo de errores puede simplificar la estructura de un compilador y
mejorar su respuesta a los errores.
Se sabe que los programas pueden contener errores de muy diverso tipo. Por ejemplo, los errores pueden
ser:
 léxicos, como escribir mal un identificador, palabra clave u operador.
 sintácticos, como una expresión aritmética con paréntesis no equilibrados.
 semánticos, como un operador aplicado a un operando incompatible.
 lógicos, como una llamada infinitamente recursiva.

Precedencia de operadores: Considere la expresión 9+5*2. Hay dos interpretaciones posibles de esta
expresión: (9+5)*2 o 9+(5*2).
Se dice que * tiene mayor precedencia que + si * considera sus operandos antes de que lo haga +. En
aritmética elemental, la multiplicación y división tienen mayor precedencia que la adición y sustracción.
Por tanto, 5 es considerado por * en 9+5*2 y en 9*5+2; es decir, las expresiones son equivalentes a
9+(5*2) y (9*5)+2, respectivamente.

Tabla de símbolos

Una tabla de símbolos es una estructura de datos que contiene un registro por cada identificador, con los
campos para los atributos del identificador (ámbito, enlace de los nombre, etc.). Se examina la tabla de
símbolos cada vez que se encuentra un nombre en el texto fuente. Si se descubre un nombre nuevo o
nueva información sobre un nombre ya existente, se produce cambios en la tabla. La estructura de datos
permite encontrar rápidamente el registro de cada identificador y almacenar o consultar rápidamente
datos de ese registro. Veamos un ejemplo de una traducción de una proposición.

- 50 -
posición :=inicial+velocidad*60

Analizador léxico

id1 := id2 + id3 * 60

Analizador sintáctico

TABLA DE SÍMBOLOS
:=
1 posición *******
id1 +
2 inicial *******
id2 *
3 velocidad *******
id3 60
4

Generador de código

MOV R2, id3


MUL R2, 60
MOV R1, id2
ADD R1, R2
MOV id1, R1

Representación de la información sobre el ámbito: Las entradas de la tabla de símbolos son para
declaraciones de nombres. Cuando se busca el caso de un nombre del texto fuente en la tabla de
símbolos, se debe devolver la entrada correspondiente a la declaración adecuada de dicho nombre. Las
reglas de ámbito del lenguaje fuente determinan qué declaración es la apropiada.
Un enfoque sencillo consiste en mantener una tabla de símbolos distinta para cada ámbito. En realidad, la
tabla de símbolos para un procedimiento o ámbito es el equivalente durante la compilación de un registro
de activación. Se encuentra la información para los nombres no locales de un procedimiento examinando
las tablas de símbolos correspondientes a los procedimientos abarcadores siguiendo las reglas de ámbito
del lenguaje. Asimismo, se puede asociar la información sobre los nombres locales de un procedimiento al
nodo correspondiente al procedimiento en un árbol sintáctico del programa. Con este enfoque la tabla de
símbolos se integra en la representación inmediata de la entrada.

Optimización de código

La fase de optimización de código trata de mejorar el código, de modo que resulte un código de máquina
más rápido de ejecutar. Algunas optimizaciones son triviales.

Generación de código

La fase final de un compilador es la generación de código objeto, que por lo general consiste en código
assembler.

ESTRUCTURA DE LA CPU PENTIUM

Coprocesador Aritmético o Matemático (Punto Flotante)


Es una pila de registros de 80 bits de ancho y 8 de entrada. Es un stack físico.
Cuando carga / borra algo, se desplaza hacia abajo / arriba.
Se usa para guardar resultados intermedios que es más rápido que hacerlo en memoria.

Asignaciones de bits:

- 51 -
32 para enteros
32 para reales
64 para double
80 para BCD (18 dígitos + 2 exponente (10 99 o 10-99)

Esa estructura, maneja las siguientes instrucciones:

Instrucción Descripción
FLD dato Manda el contenido de una dirección al stack.
FST dato Copia de la pila a la memoria.
FSUB Resta el tope de la pila.
FADD Suma el tope de la pila.
FXCH dato Intercambia el contenido de la pila. Toma el
contenido de la primer dirección, lo pone en dato y lo
de dato lo manda a la primer dirección de la pila.
FSTP dato Copia el contenido de la primer dirección de la pila al
dato y borra ese dato de la pila.
FSUB dato El primer lugar de la pila menos un dato.
FADD dato El primer lugar de la pila más un dato.
FSUB ST (5) Al primer lugar, restarle lo que está en la quinta
posición de la pila.
FADD ST (5) Al primer lugar, sumarle lo que está en la quinta
posición de la pila.
FADD El primero más el segundo.

Siempre se hacen operaciones con el dato que esta en el topo de la pila.

Las siguientes operaciones de punto flotante, son de 32 bits.

Instrucción Descripción
FILD Carga enteros de 32 bits.
FIST Almacenamiento de enteros de 32 bits.
FBLD
FBST Igual a las instrucciones descritas anteriormente,
FBSTP pero intercalando una “B”. La diferencia es que
FBADD manejan datos de 32 bits.
FBSUB
FDLD Opera con instrucciones de 64 bits (se intercala una
FDST D).
FDSTP Aclaración: el profesor no esta seguro si es una
FDADD D lo que se intercala.
FDSUB
Para todos los casos, FMUL (multiplicación) y FDIV (división). 16, 32 y 80 bits. No existe la noción de
acarreo, si la de overflow.

El contenido de la pila se puede manipular con ST (n).

Ejemplo:

FLD ST (b) Toma lo del cuarto lugar de la pila, lo lleva al primer y todo baja de
lugar.
- 52 -
AA 0
CC 1
BB 2
XX 3
...

FLD ST (3)

XX 0
AA 1
BB 2
CC 3
...

Multiplicación

La suma y resta de enteros, es simétrica y se realiza en cualquier registro.


La suma y resta flotante, se realiza en el topo de la pila.

En cambio, la multiplicación, se hace siempre sobre AL si son 8 bits, sobre AX si son 16 bits y sobre
EDX si son 32 bits.

Resultados:

8 bits x 8 bits: 16 bits


16 bits x 16 bits: 32 bits
32 bits x 32 bits: 64 bits

El lenguaje no da como resultado eso, el procesador dá 16, 32 y 64 en la multiplicación.

Ejemplos:
Instrucción bits registros donde queda
Multiplicación MUL CL 8 AX * CL AX
enteros sin
signo
MUL BX 16 AX * BX DX:AX
MUL ECX 32 EAX * ECX EDX:EAX

Multiplicación IMUL CL
enteros con
signo
IMUL BX
IMUL EBX

- 53 -
- 54 -

También podría gustarte