Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
ANTOLOGÍA
LENGUAJES Y AUTOMATAS II
0
Instituto Tecnológico Superior de Misantla
INDICE
Introduccion
Objetivos del Curso
1. Análisis semántico
1.1. Arboles de expresiones.
1.2. Acciones semánticas de un analizador sintáctico.
1.3. Comprobaciones de tipos en expresiones.
1.4. Pila semántica en un analizador sintáctico.
1.5. Esquema de traducción.
1.6. Generación de la tabla de símbolo y de direcciones.
1.7. Manejo de errores semánticos.
3. Optimización
3.1. Tipos de optimización.
3.1.1. Locales.
3.1.2. Ciclos.
3.1.3. Globales.
3.1.4. De mirilla.
3.2. Costos.
3.2.1. Costo de ejecución. (memoria, registros, pilas)
3.2.2. Criterios para mejorar el código.
1
Instituto Tecnológico Superior de Misantla
Evaluación
Bibliografia
2
Instituto Tecnológico Superior de Misantla
3
Instituto Tecnológico Superior de Misantla
Introducción
El lenguaje es un vehículo por el cual se transmiten instrucciones a un
procesador para que las ejecute y produzca ciertos resultados.
4
Instituto Tecnológico Superior de Misantla
Una vez que se tienen registradas las entidades que se usarán en el programa,
el siguiente paso es emplear este registro para la verificación de los tipos y el análisis
semántico de las operaciones; esto ocurre principalmente en donde se usan
expresiones, como veremos en el caso de la operación de asignación.
Junto con el registro de cada entidad, debemos tener definido una tabla donde
se relacionan los tipos en cada operación para determinar el tipo resultante (alguno de
los tipos simples, en caso que la operación es posible o ERROR cuando no puede
realizarse).
5
Instituto Tecnológico Superior de Misantla
Las reglas para representar una expresión mediante un árbol son las siguientes:
1. Cada hoja está etiquetada con un operando y solo consta de ese operando.
2. Cada nodo interior “n” está etiquetado con un solo operador.
3. Las hojas están representadas por los operandos y los nodos por la raíz de
cada árbol.
Consenso
Un conjunto de valores
Un conjunto de operadores sobre los valores
Las clases son una instanciación moderna de la noción de tipo.
Ciertas Operaciones son legales para cada tipo
No tiene sentido sumar un apuntador a función y un entero en Java
Tiene sentido sumar dos enteros
6
Instituto Tecnológico Superior de Misantla
7
Instituto Tecnológico Superior de Misantla
Java es un lenguaje con un sistema de tipos más fuerte que C/C++ que lo hace
más seguro pero a la vez un poco más deficiente.
Los lenguajes interpretados en el pasado eran más susceptibles a errores en
tiempo de ejecución pero las cosas han cambiado.
En general la tabla de símbolos debe contener la siguiente estructura para
validar la parte semántica:
Símbolo {
nombre;
tipo;
ámbito;
}
8
Instituto Tecnológico Superior de Misantla
9
Instituto Tecnológico Superior de Misantla
Los atributos requeridos para cada símbolo depende a nivel general si:
del tipo de gestión de memoria,
el lenguaje está, o no, estructurado en bloques,
el símbolo es, o no, parámetros de un procedimiento o función.
El analizador semántico:
Añadir los tipos, si procede, a los símbolos que aparecen en la
tabla de símbolos.
Cuando se hace una implantación de una única pasada, entonces los símbolos
son insertados y calificados, a nivel semántica y de generación de código, por el
analizador sintáctico y en el mismo instante que son detectados por el analizador léxico
y que son pasados al analizador sintáctico.
INSERTAR
CONSULTAR
MODIFICAR (añadir atributos nuevos)
10
Instituto Tecnológico Superior de Misantla
11
Instituto Tecnológico Superior de Misantla
error que informa del problema. En Visual Studio, estos mensajes de error aparecen en
la ventana de resultados. Los mensajes indican la ubicación del error de sintaxis
(número de línea y archivo), y muestran una breve descripción del problema.
Normalmente, la determinación de la causa de un error de sintaxis es un proceso
sencillo cuando se conocen estas descripciones. Para obtener más información, vea
Ventana de resultados.
Los errores semánticos son más sutiles. Un error semántico se produce cuando
la sintaxis del código es correcta, pero la semántica o significado no es el que se
pretendía. La construcción obedece las reglas del lenguaje, y por ello el compilador o
intérprete no detectan los errores semánticos. Los compiladores e intérpretes sólo se
ocupan de la estructura del código que se escribe, y no de su significado. Un error
semántico puede hacer que el programa termine de forma anormal, con o sin un
mensaje de error. Hablando en términos coloquiales, puede hacer que el equipo se
quede "colgado".
Sin embargo, no todos los errores semánticos se manifiestan de una forma tan
obvia. Un programa puede continuar en ejecución después de haberse producido
errores semánticos, pero su estado interno puede ser distinto del esperado. Quizá las
variables no contengan los datos correctos, o bien es posible que el programa siga un
camino distinto del pretendido. Eventualmente, la consecuencia será un resultado
incorrecto. Estos errores se denominan lógicos, ya que aunque el programa no se
bloquea, la lógica que representan contiene un error.
La única forma de detectar los errores lógicos es probar el programa, ya sea
manual o automáticamente, y comprobar que el resultado es el esperado. Para obtener
más información, vea Probar y optimizar. Las pruebas deben ser una parte integrante
del proceso de desarrollo de software. Desgraciadamente, aunque las pruebas pueden
indicar que el resultado de un programa es incorrecto, normalmente no proporcionarán
ninguna pista acerca de qué parte del código ha causado realmente el problema. Es
aquí donde comienza la depuración.
Normalmente, aunque no siempre, la depuración implicará el uso de un
depurador, una eficaz herramienta que permite observar el comportamiento del
programa en tiempo de ejecución y determinar la ubicación de los errores semánticos.
También es posible utilizar ciertas funciones de depuración integradas en el lenguaje y
sus bibliotecas asociadas. Muchos programadores tienen su primer contacto con la
depuración cuando intentan aislar un problema agregando al código llamadas a
funciones de salida, como printf o MsgBox. Esta técnica de depuración es
perfectamente legítima, pero hace que una vez encontrado y resuelto el problema sea
necesario volver a recorrer el código para eliminar las llamadas a funciones
adicionales. Ocasionalmente, puede encontrarse una situación en la que, al agregar
código nuevo ( incluso una simple llamada a printf o MsgBox), el comportamiento del
código que se intenta depurar cambia.
Con un depurador puede examinar el contenido de las variables del programa
sin tener que insertar llamadas adicionales a funciones que lo muestren. Puede insertar
un punto de interrupción en el código para detener la ejecución en el punto que le
interese. Cuando el programa esté detenido (en modo de interrupción), puede examinar
las variables locales u otros datos relevantes a través de medios tales como la ventana
Inspección, el cuadro de diálogo Inspección rápida y la ventana Memoria. Para obtener
12
Instituto Tecnológico Superior de Misantla
Lenguajes intermedios
Un lenguaje intermedio se puede definir como una manera de representar
procedimientos y estructuras de datos que sirva como entrada para una MV en alguna
parte de su jerarquía, entre el lenguaje de entrada (el nivel más alto) y el código
ejecutado en la máquina (el nivel más bajo) - tanto en el tiempo de compilación como
en el de ejecución (lo que se puede ver en la figura 1).
Para considerar el papel de los lenguajes intermedios y sus ventajas y
desventajas, conviene destacar la diferencia entre la traducción de un lenguaje de alto
nivel a código máquina anteriormente a su ejecución (su compilación) y su
interpretación, es decir, la conversión de cada instrucción del lenguaje a código
13
Instituto Tecnológico Superior de Misantla
máquina y su ejecución, una por una, al ejecutar el programa. Este proceso se realiza a
través de una MV de interpretación que simula un ordenador cuyo código máquina es
el lenguaje de alto nivel que está siendo interpretado. Y típicamente, esta MV se
construye a través de un conjunto de programas de código máquina que representa los
algoritmos y estructuras de datos necesarios para la ejecución de las instrucciones del
lenguaje de alto nivel. Hay ventajas y desventajas en cada manera de convertir los
lenguajes de alto nivel a código máquina, que se pueden resumir así:
Estos dos casos representan los dos extremos porque, como ya se ha visto,
existe también lo que se llama la compilación parcial, que es una mezcla de los dos
enfoques, donde se compila el lenguaje de alto nivel a un lenguaje intermedio (más
cerca de las estructuras presentes en el código máquina que las del código fuente) y
luego se interpreta este lenguaje al ejecutar el programa. Como puede imaginarse, esta
técnica combina las ventajas y desventajas de los dos enfoques anteriores. Un ejemplo
de esta combinación existe en el lenguaje de programación Java y su entorno.
Entre otras cosas, Java empezó con la idea de liberar al programador de las
dificultades de portar su aplicación a nuevas plataformas lo cual, si el programa está
muy vinculado a algún aspecto del sistema operativo donde fue escrito, podría ser muy
difícil. Se compilará el código fuente de Java a un código byte (bytecode) antes de
ejecutarlo. Y a la hora de correr el programa, este código, como lenguaje intermedio,
sería el lenguaje de entrada para una MV, que con un conjunto de librerías (el entorno
de ejecución de Java, Java Runtime o JRE), la interpretaría para su ejecución. Por lo
tanto, este bytecode podría correr en cualquier hardware donde haya una versión del
JRE disponible. Como este bytecode está más cerca del nivel de máquina que de un
lenguaje de alto nivel, los programas correrán más rápidamente que los programas
completamente interpretados, aunque más despacio que los programas previamente
compilados al código máquina.
Como se puede ver en la figura 1, tanto los programas compilados parcialmente
a un lenguaje intermedio (como Java) como los programas escritos en lenguajes de
alto nivel que se interpretan (como Lisp) requieren una MV para interpretar el
programa. La principal ventaja del lenguaje intermedio en este caso es su proximidad al
nivel del código máquina, en el sentido de que supone menos trabajo a la hora de
ejecutarlo y, por lo tanto, los programas corren más rápidamente que los puramente
interpretados.
Además del papel de los lenguajes intermedios en la compilación parcial, se
puede destacar su papel en la compilación estándar. Como ejemplo se puede
considerar C como lenguaje intermedio para un lenguaje compilado nuevo (como se
14
Instituto Tecnológico Superior de Misantla
planteó en la sección 1.5.1 para C++). Si el autor de un nuevo lenguaje decide utilizar
C, por ejemplo, como su lenguaje intermedio, sólo tendrá que implementar una MV
para convertir el código fuente de su lenguaje a C, ahorrando mucho trabajo. Además
de las razones dadas antes, las ventajas de utilizar un lenguaje tan establecido como C
como lenguaje intermedio son:
• La facilidad de portar el lenguaje a una nueva máquina (sólo hay que tener un
compilador C disponible allí).
• La generación de código máquina es una tarea muy compleja que requiere un
conocimiento profundo de la arquitectura de la máquina en cuestión – y de cada
máquina en que se quiere una versión del lenguaje.
• La facilidad de modificar algún rasgo del comportamiento del lenguaje en
alguna máquina en concreto (por ejemplo, características de memoria o rendimiento –
se pueden añadir librerías C customizadas sin grandes problemas).
• Las posibilidades disponibles para mapear estructuras intermedias del nuevo
lenguaje a estructuras de datos de C.
Y las desventajas son:
• La depuración es muy difícil porque, entre otras cosas, los errores que ocurren
en el código C no son muy fáciles de localizar en lo que ha escrito el programador
originalmente en el nuevo lenguaje.
• Las características de rendimiento y eficiencia del lenguaje están determinadas
por el compilador C.
• Habrá ocasiones en las que no exista una buena traducción entre una
estructura en el nuevo lenguaje y las estructuras de datos en C, por lo que habrá una
pérdida de eficiencia en el programa resultante (como, por ejemplo, ocurre en la
mayoría de las ocasiones en que se compilan estructuras de Prolog a C – sólo se
puede expresar iteración en Prolog utilizando recursión).
2.2 Notaciones
NOTACION INFIJA
Es la forma mas común que utilizamos para escribir expresiones matemáticas,
estas notaciones se refiere a que el operador esta entre los operandos. La notación
infija puede estar completamente parentizada o puede basarse en un esquema de
precedencia de operadores así como el uso de paréntesis para invalidar los arreglos al
expresar el orden de evaluación de una expresión:
15
Instituto Tecnológico Superior de Misantla
3*4=12
3*4+2=14
3*(4+2)=18
Notación sufija
Llamada también postfija o polaca inversa, se usa para representar expresiones
sin necesidad de paréntesis.
Ejemplos:
a*b ab*
a*(b+c/d) abcd/+*
a*b+c*d ab*cd*+
Los identificadores aparecen en el mismo orden. Los operadores en el de
evaluación (de izquierda a derecha).
Problema: operadores monádicos (unarios). O bien se transforman en diádicos
(binarios) o se cambia el símbolo.
Ejemplo: -a se convierte en 0-a o en @a
a*(-b+c/d) ab@cd/+*
Existen dos problemas principales:
Construir la notación sufija a partir de la infija.
Analizar la notación sufija en el segundo paso de la compilación.
16
Instituto Tecnológico Superior de Misantla
NOTACION POSFIJA
Como su nombre lo indica se refiere a que el operador ocupa la posición
después de los operandos sus características principales son: el orden de los
operandos se conserva igual que la expresión infija equivalente no utiliza paréntesis ya
que no es una operación ambigua.
17
Instituto Tecnológico Superior de Misantla
(A+B)*C AB+C*
Notación prefija
Ordinaria * ( + ( a , b ) , - ( c , d ) )
Cambridge ( * (+ a b ) (- c d ) ) {Lenguaje Lisp}
Polaca * + a b - c d
18
Instituto Tecnológico Superior de Misantla
2.- La notación prefija se puede usar para representar operaciones con cualquier
numero de operandos y es, por tanto, completamente general
3.- Es relativamente fácil decodificar mecánicamente; por esto se consigue con
facilidad expresiones prefijas a secuencias de código simple.
Expresiones Aritméticas.
19
Instituto Tecnológico Superior de Misantla
1. Procesa el nodo.
2. Procesa por el sucesor izquierdo.
3. Procesa por el sucesor derecho.
EJEMPLO:
x a b * a b * + :=
Código P (P-code)
El código-P (abreviatura de "código empaquetado") se genera para optimizar la
velocidad y el tamaño del código. El código-P puede reducir de forma significativa el
tamaño de un programa y la velocidad de ejecución en más de un 60 por lOO. Mejor
aún, todo esto se ejecuta simplemente cuando se activa una opción específica del
compilador Esto significa que cualquier código escrito en C o en C++ se puede
compilar normalmente o con código-P.
Esta tecnología compila un código fuente de una aplicación dentro del "código
objeto interpretado", que es una representación más condensada del código objeto y
con un nivel mayor El proceso finaliza cuando se enlaza un pequeño módulo intérprete
con la aplicación.
Sin embargo, la utilización eficiente de esta tecnología requiere cierta habilidad.
Como el intérprete genera código objeto en tiempo de ejecución, el código-P se ejecuta
más lentamente que el código objeto origen. Con una utilización muy cuidadosa de la
20
Instituto Tecnológico Superior de Misantla
directiva #pragma, una aplicación puede generar código-P para funciones críticas en
cuanto a espacio y pasar a generar código origen para funciones de velocidad crítica.
Las mejores candidatas para la generación de código-P son aquellas rutinas que
tratan con la interfaz de usuario, y como muchas aplicaciones Windows emplean el 50
por 100 de su tiempo generando la interfaz de usuario, el código-P proporciona las
características de rendimiento óptimas.
Tripletes
No se pone el resultado, se sustituye por referencias a tripletes. Por ejemplo: la
expresión a*b+c*d equivale a:
(1) (*,a,b)
(2) (*,c,d)
(3) (+,(1),(2))
mientras que a*b+1 equivale a:
(1) (*,a,b)
(2) (*,(1),1)
Tripletes indirectos: se numeran arbitrariamente los tripletes y se da el orden de
ejecución. Ejemplo, sean las instrucciones:
a := b*c
b := b*c
Equivalen a los tripletes
(1) (*,b,c)
(2) (:=,(1),a)
(3) (:=,(1),b)
y el orden de ejecución es (1),(2),(1),(3). Esta forma es útil para preparar la
optimización de código. Si hay que alterar el orden de las operaciones o eliminar
alguna, es más fácil hacerlo ahí.
Cuádruplas
Una operación diádica se puede representar mediante la cuádrupla
(<Operador>, <Operando1>, <Operando2>, <Resultado>)
Ejemplo:
(*,A,B,T)
Una expresión se puede representar mediante un conjunto de cuádruplas.
Ejemplo: la expresión a*b+c*d equivale a:
(*,a,b,t1)
(*,c,d,t2)
(+,t1,t2,t3)
Ejemplo: la expresión c:=a[i;b[j]] equivale a:
(*,i,d1,t1)
(+,t1,b[j],t2)
(:=,a[t2],,c)
21
Instituto Tecnológico Superior de Misantla
22
Instituto Tecnológico Superior de Misantla
23
Instituto Tecnológico Superior de Misantla
24
Instituto Tecnológico Superior de Misantla
i++;
i = T (cadena, i);
cuad (cadena[j], pop(), pop(), gen(Ti));
push (Ti);
break;
}
return i;
}
25
Instituto Tecnológico Superior de Misantla
}
(+, O1, O2, R) se traduce por SUMA (O1, O2, R).
La multiplicación se haría igual, sustituyendo "ADD" por "MUL".
Para funciones diádicas no conmutativas:
RESTA (opd *x, opd *y, opd *z)
{
CAC (x, NULL);
GEN ("SUB AC,",y);
AC=z;
}
(-, O1, O2, R) se traduce por RESTA (O1, O2, R).
La división se haría igual, sustituyendo "SUB" por "DIV".
Para funciones monádicas:
NEG (opd *x, opd *z)
{
CAC (x, NULL);
GEN ("NEG AC");
AC=z;
}
(@, O1,, R) se traduce por NEG (O1, R).
Ejemplo: sea la expresión A*((A*B+C)-C*D)
Las cuádruplas son:
Cuádrupla Se genera Valor de AC
------------ ---------- -----------
(*,A,B,T1) MOV AC,A A
MUL AC,B T1
(+,T1,C,T2) ADD AC,C T2
(*,C,D,T3) MOV T2,AC
MOV AC,C C
MUL AC,D T3
(-,T2,T3,T4) MOV T3,AC
MOV AC,T2 T2
SUB AC,T3 T4
(*,A,T4,T5) MUL AC,A T5
26
Instituto Tecnológico Superior de Misantla
MOV A,AC
La asignación puede llevar implícitas diversas conversiones de tipo, que pueden
haber sido tratadas previamente por el analizador semántico.
También puede haber conversión implícita de la profundidad de los punteros,
como en
ref int A;
int B;
...
B := A;
En este caso, el código generado debería ser:
MOV índice,A
MOV AC,[índice]
MOV B,AC
Constantes
def: Una constante es un dato cuyo valor no puede cambiar durante la
ejecución del programa. Recibe un valor en el momento de la compilación y este
permanece inalterado durante todo el programa.
Como ya se ha comentado en el tema sobre las partes de un programa, las
constantes se declaran en una sección que comienza con la palabra reservada const.
Después de declarar una constante ya puedes usarla en el cuerpo principal del
programa. Tienen varios usos: ser miembro en una expresion, en una comparación,
asignar su valor a una variable, etc.
27
Instituto Tecnológico Superior de Misantla
También llamadas constantes con nombre, son las que se declaran en la sección
const asignándoles un valor directamente. Por ejemplo:
const
Pi = 3.141592; (* valor real *)
Min = 0; (* entero *)
Max = 99; (* entero *)
Saludo = 'Hola'; (* cadena caract. *)
Constantes expresión
Variables
def: Una variable es un nombre asociado a un elemento de datos que está
situado en posiciones contiguas de la memoria principal, y su valor puede cambiar
durante la ejecución de un programa.
Toda variable pertenece a un tipo de dato concreto. En la declaración de una
variable se debe indicar el tipo al que pertenece. Así tendremos variables enteras,
28
Instituto Tecnológico Superior de Misantla
reales, booleanas, etc. Por otro lado, distinguimos tres partes fundamentales en la vida
de una variable:
Declaración
Iniciación
Utilización
Declaración de variables
Esto no es más que darle un valor inicial a una variable. Así como lo primero que se
hace con una variable es declararla, lo siguiente tiene que ser iniciarla. Esto se hace
para evitar posibles errores en tiempo de ejecución, pues una variable tiene un valor
indeterminado después de declararla. Principalmente, existen dos maneras de otorgar
valores iniciales a variables:
Mediante una sentencia de asignación
Mediante uno de los procedimientos de entrada de datos (read o
readln)
Veamos un ejemplo que reúne los dos casos:
begin
...
i:=1;
readln(n);
while i < n do begin
(* cuerpo del bucle *)
i := i + 1
end;
...
end.
Utilización de variables
Una vez declarada e iniciada una variable, es el momento de utilizarla. Esta es la parte
que presenta un mayor abanico de posibilidades. A continuación tienes unas cuantas:
Incrementar su valor:
i := i + 1
Controlar un bucle:
for i:=1 to 10 do ...
Chequear una condición:
if i<10 then ...
29
Instituto Tecnológico Superior de Misantla
lista-variables-n: tipo-n;
donde:
VAR es una palabra reservada;
cada lista-variables-i es una única variable o
una lista de variables separadas por comas;
30
Instituto Tecnológico Superior de Misantla
C, K: constant Integer := 0;
package Ejemplo is
C: constant Integer; -- declaración diferida
...
private
C: constant Integer := 0; -- declaración completa
...
end Ejemplo;
Ada contempla además una clase de constantes que sirve para asociar un
nombre para identificar un valor numérico y que no requiere la especificación de un
tipo.
31
Instituto Tecnológico Superior de Misantla
P := new Integer; --P debe ser un puntero (access) a variables de tipo Integer.
identificador-n= constante-n;
donde:
32
Instituto Tecnológico Superior de Misantla
33
Instituto Tecnológico Superior de Misantla
main()
{
int t,i,num[3][4];
}
En C se permite la inicialización de arreglos, debiendo seguir el siguiente
formato:
tipo nombre_arr[ tam1 ][ tam2 ] ... [ tamN] = {lista-valores};
Por ejemplo:
int i[10] = {1,2,3,4,5,6,7,8,9,10};
int num[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
Arreglos
Un arreglo es una lista ordenada de datos escalares. Los arreglos pueden tener
cualquier número de elementos. Los elementos de un arreglo son escalares que
empiezan por $, cuyos subíndices empiezan por 0.
Representación
Un arreglo es una lista de valores separados por coma, y dentro de paréntesis.
Los valores de los elementos pueden ser de distinto tipo.
@num = (1,2,3);
@var = ("juan",4.5);
@num = ($a,17);
@var = ($b+$c,$d+$e);
34
Instituto Tecnológico Superior de Misantla
@a=(7,8,9);
$b=$a[0]; # asigna 7 a $b
$a[1]=5; # ahora @a es (7,5,9)
$a[2]++; # suma uno al tercer valor de @a siendo ahora (7,5,10)
$c=$a[0]+$a[1]; # asigna 12 a $c
@juan=(7,8,9);
$a=2;
$b=$juan[$a]; # $b = 9
$c=$juan[$a-1]; # $c = 8
Si se accesa un elemento del arreglo mas allá del fin del arreglo el valor undef es
retornado.
@a=(1,2,3);
$b=$a[7]; # $b = 0 o "" , dependiendo del contexto
Así mismo, asignar un valor a un elemento más alla del fin del arreglo, agranda
el arreglo asignando undef a los valores intermedios.
@a=(1,2,3);
$a[3]="hola"; # @a es ahora (1,2,3,"hola")
$a[6]="chao"; # @a es ahora (1,2,3,"hola",undef,undef,"chao")
@a=('b',3,"a"); # $#a = 2
$d=$a[$#a]; # $d='a'
$c=$#a+5; # $c=7
push() y pop()
35
Instituto Tecnológico Superior de Misantla
push() es utilizado para agregar y pop() para sacar elementos por el lado
derecho del arreglo.
@lista=(1,2,3);
push(@lista,5); # @lista=(1,2,3,5)
$a=pop(@lista); # $a=5, @a=(1,2,3)
shift() y unshift()
Al igual que pop() y push(), éstos extraen e insertan elementos de un
arreglo, pero lo hacen por el lado izquierdo.
@a=(1,2,3);
unshift(@a,0); # @a=(0,1,2,3);
$x=shift(@a); # $x=0 , @a=(1,2,3)
unshift(@a,5,6,7) # @a=(5,6,7,1,2,3)
reverse()
reverse() invierte el orden de los elementos de un arreglo, retornando la
lista invertida.
@a=(1,2,3);
@b=reverse(@a); # @b = (3,2,1)
@c=reverse(1,2,3); # @c = (3,2,1)
@a=reverse(@a) ;
chop()
chop() le saca el último caracter a cada elemento de un arreglo.
@a=("hola","mundo\n","felices dias");
chop(@a); # @a=("hol","mundo","felices dia")
join()
join() combina los elementos de una lista y retorna un escalar donde cada
elemento está separado por el valor que se haya pasado como parámetro.
@campos=(1,2,"hola");
$salida=join(".",@campos); # $salida="1.2.hola"
36
Instituto Tecnológico Superior de Misantla
split()
split() usa una expresión regular para dividir un escalar u otra expresión
de cadena para analizar una cadena, retorna una lista o arreglo.
$linea="mvargas::117:10:Juan";
@a=split(/:/,$line); # @a=("mvargas","","117","10","Juan")
grep()
grep() funciona igual que la orden grep de Unix, excepto que opera sobre
una lista y no sobre un archivo.
Operaciones raras
@a=(1,2,3,4,5);
($b,$c,$d)=@a; # b=1 c=2 d=3
@a=(1,'b',2,'c');
($b,@a)=@a; # b=1 a=('b',2,'c')
@a=('hola','tu');
@b=(1,2,@a,7,8); # b=(1,2,'hola','tu',7,8)
@a=(1,2,3,4,5);
@b=@a[1,3]; # b=(2,3,4)
@b=(1,2,3,4,6)[0,1]; # b=(1,2)
@a=(7,8,9);
@b=(1,2,0);
@c=@a[@b]; # c=(8,9,7)
#!/usr/bin/perl
# Ejemplo 6 : Demostración de arreglos
37
Instituto Tecnológico Superior de Misantla
push(@lista,5); # @lista=(1,2,3,5)
print "@lista\n";
print "@a\n";
print "@b\n";
Funciones
Una función es un conjunto de declaraciones, definiciones, expresiones y
sentencias que realizan una tarea específica.
El formato general de una función en C es
especificador_de_tipo nombre_de_función( lista_de_parámetros )
{
variables locales
código de la función
}
El especificador_de_tipo indica el tipo del valor que la función devolverá
mediante el uso de return. El valor puede ser de cualquier tipo válido. Si no se
específica un valor, entonces la computadora asume por defecto que la función
devolverá un resultado entero. No se tienen siempre que incluir parámetros en una
función. la lista de parámetros puede estar vacía.
Las funciones terminan y regresan automáticamente al procedimiento que las
llamó cuando se encuentra la última llave }, o bien, se puede forzar el regreso antes
38
Instituto Tecnológico Superior de Misantla
usando la sentencia return. Ademas del uso señalado la función return se usa para
devolver un valor.
Se examina a continuación un ejemplo que encuentra el promedio de dos
enteros:
float encontprom(int num1, int num2)
{
float promedio;
main()
{
int a=7, b=10;
float resultado;
Funciones void
Las funciones void dan una forma de emular, lo que en otros lenguajes se
conocen como procedimientos (por ejemplo, en PASCAL). Se usan cuando no requiere
regresar un valor. Se muestra un ejemplo que imprime los cuadrados de ciertos
números.
void cuadrados()
{
int contador;
main()
{
cuadrados();
}
En la función cuadrados no esta definido ningún parámetro, y por otra parte
tampoco se emplea la sentencia return para regresar de la función.
Funciones y arreglos
Cuando se usan un arreglo como un argumento a la función, se pasa sólo la
dirección del arreglo y no la copia del arreglo entero. Para fines prácticos podemos
considerar el nombre del arreglo sin ningún índice como la dirección del arreglo.
39
Instituto Tecnológico Superior de Misantla
main()
{
char nombre[]="Facultad";
imp_rev(nombre);
}
Observar que en la función imp_rev se usa la función strlen para calcular la
longitud de la cadena sin incluir el terminador nulo. Por otra parte, la función imp_rev no
usa la sentencia return ni para terminar de usar la función, ni para regresar algún valor.
Se muestra otro ejemplo,
float enconprom(int tam, float lista[])
{
int i;
float suma = 0.0;
main()
{
float numeros[]={2.3, 8.0, 15.0, 20.2, 44.01, -3.0, -2.9};
40
Instituto Tecnológico Superior de Misantla
Prototipos de funciones
Antes de usar una función C debe tener conocimiento acerca del tipo de dato
que regresará y el tipo de los parámetros que la función espera.
El estándar ANSI de C introdujó una nueva (mejor) forma de hacer lo anterior
respecto a las versiones previas de C.
La importancia de usar prototipos de funciones es la siguiente:
Se hace el código más estructurado y por lo tanto, más fácil de leer.
Se permite al compilador de C revisar la sintaxis de las funciones
llamadas.
Lo anterior es hecho, dependiendo del alcance de la función. Básicamente si una
función ha sido definida antes de que sea usada (o llamada), entonces se puede usar
la función sin problemas.
Si no es así, entonces la función se debe declarar. La declaración simplemente
maneja el tipo de dato que la función regresa y el tipo de par^o'ametros usados por la
función.
Es una práctica usual y conveniente escribir el prototipo de todas las funciones al
principio del programa, sin embargo esto no es estrictamente necesario.
Para declarar un prototipo de una función se indicará el tipo de dato que
regresará la función, el nombre de la función y entre paréntesis la lista del tipo de los
parámetros de acuerdo al orden que aparecen en la definición de la función. Por
ejemplo:
int longcad(char []);
Lo anterior declara una función llamada longcad que regresa un valor entero y
acepta una cadena como parámetro.
Ejercicios
1. Escribir una función ``reemplaza'', la cual toma una cadena como
parámetro, le reemplaza todos los espacios de la cadena por un guión bajo, y
devuelve el número de espacios reemplazados. Por ejemplo:
2. char cadena[] = "El gato negro";
3. n = reemplaza( cadena );
deberá devolver:
cadena convertida "El_gato_negro"
n=2
4. Escribir un programa que lea una línea de texto en un buffer (una
cadena de caracteres) usando la función gets y calcule la longitud de la línea
(NO usar la función strlen).
5. Modificar el programa anterior para que lea un archivo de texto. El
archivo deberá redireccionarse al programa, debiendo mostrar el contenido del
41
Instituto Tecnológico Superior de Misantla
mismo. En caso de que se lea una línea con longitud 0 deberá terminar el
programa.
Unidad 3. Optimizacion
42
Instituto Tecnológico Superior de Misantla
43
Instituto Tecnológico Superior de Misantla
o MUL
Sin embargo si se hace la operación con las constantes sólo quedaría
PUSH 25.
Fusión de constantes. suele realizarse con las constantes de
cadena. Así el uso de la misma constante dos o más veces tan sólo genera
código para una constante, el resto son la referencia a la misma constante.
Reacondicionamiento de instrucciones. También se cambia en
algunos casos en algunos casos el orden de evaluación, para generar código
optimizado.
Comprobación de los rangos y de desbordamiento de la pila. Una
vez que los programas compilados han sido probados, pueden quitarse del
compilador las opciones de comprobación de rangos y las opciones de
comprobación de desbordamientos de la pila. Al generar el código
correspondiente para generar dichas comprobaciones.
Uso de operadores de desplazamiento de bits en vez multiplicación
y división. Las operaciones como x*a, donde a es una constante y potencia de 2,
se puede generar código con operaciones de desplazamiento. Lo mismo ocurre
para operaciones de la forma x/a.
Eliminación de código muerto. El código que no se ejecuta no debe
generar código. Así los bucles vacíos no generan código.
44
Instituto Tecnológico Superior de Misantla
Direccionamiento.
-LENGUAJE MÁQUINA-
45
Instituto Tecnológico Superior de Misantla
Se dice que un programa se forma con un grupo de patrones binarios, por ejemplo sumar las
localidades 1500H y 3020H y almacenar el resultado en la localidad 3000H el programa a
ejecutar es el siguiente:
0 1
011 010
0 0
000 000
0 0
001 101
0 0
100 111
0 1
011 010
0 0
010 000
0 0
011 000
1 0
000 000
0 0
011 010
0 0
000 000
0 0
011 000
A estos patrones binarios se les conoce como "LENGUAJE MÁQUINA" ya que este es
el único lenguaje que entienden las computadoras.
46
Instituto Tecnológico Superior de Misantla
-MNEMÓNICOS-
Un método de mejorar el manejo de las instrucciones para el programador consiste en
asignar un nombre a cada instrucción, por ejemplo a la instrucción de decrementar se
le nombra como "DEC" y a la de cargar un dato se nombra como "LD", a los nombres
de las instrucciones se les conoce como "MNEMÓNICOS", se escogen de tal manera
que orienten en forma fácil al programador.
Es más fácil recordar que INC A es el mnemónico de la instrucción para
incrementar en uno el contenido del registro acumulador que recordar que 3Ch es el
código operacional correspondiente a la misma instrucción, cada fabricante de
microprocesadores proporciona una serie de mnemónicos para el conjunto de
instrucciones de cada microprocesador.
-MODOS DE DIRECCIONAMIENTO-
Una parte importante en la programación es el de entender las formas de
direccionamiento para las localidades de memoria que contienen los datos que se
procesan como instrucciones, la CPU Z80 tiene 10 modos diferentes para direccionar
los datos almacenados en la memoria y en los registros:
Implícito o inherente
Inmediato
Inmediato extendido
Registro
Registro indirecto
Extendido
Página cero modificada
Relativo
Indexado
Bit
47
Instituto Tecnológico Superior de Misantla
-DIRECCIONAMIENTO INMEDIATO-
Este modo no constituye un direccionamiento propiamente dicho, pues aquí la
instrucción no contiene ninguna dirección, en su lugar figura un operando sobre el cual
se realiza la operación que indica la instrucción, no hay necesidad del ciclo de memoria
(ejecute) de ejecución, una denominación más precisa es la de operando inmediato.
Una instrucción con direccionamiento inmediato es la que contiene en el byte 2 o
en el byte 3 de la propia instrucción en el dato de la transferencia, es decir, el dato es
parte integral de los bytes que constituyen la instrucción.
x 3E LD A,N
x+1 N A 07h
48
Instituto Tecnológico Superior de Misantla
x OP-CODE
x BYTE BAJO
+1
x BYTE ALTO
+2
Este modo de direccionamiento lo utilizan las instrucciones del grupo "cargar 16 bits" y
del grupo de instrucciones "saltos, llamadas y regresos", (JP, CALL y RET).
El primero de los dos bytes del dato o de la dirección contiene los bits menos
significativos (LSB) y el segundo byte contiene los bits más significativos (MSB).
Ejemplo:
X 01
x+1 68 NN = 7968
x+2 79
Los movimientos de esta instrucción son los siguientes:
1. El contenido de la localidad "x" apuntada por el contador de
programa (PC) se carga en el registro de instrucciones IR y se incrementa el
contador de programa, (PC PC + 1)
2. La unidad de control después de decodificar la instrucción ordena
que el contenido de la localidad "x + 1" apuntada por el PC se cargue en el
registro B, y se incrementa el contador de programa ( PC PC + 1)
3. La unidad de control ordena que el contenido apuntado por el PC
se cargue en el registro C, se incrementa el contador de programa, (PC PC +
1) y el PC apunta a la siguiente instrucción a ejecutarse.
-DIRECCIONAMIENTO DE REGISTRO-
Muchas instrucciones utilizan los registros del Z80 para recibir o proporcionar un
dato, el código de la instrucción tiene un campo que se usa para especificar el ó los
registros que se utilizan en la ejecución de la instrucción.
Ejemplo: LD r,s
Esta instrucción ordena que el contenido del registro "s" se deposite en el
registro "r".
R registro destino
S registro fuente
49
Instituto Tecnológico Superior de Misantla
"r" y "s" pueden ser cualquiera de los registros activos o primarios de 8 bits de la
CPU Z80, el movimiento de la información de esta instrucción es:
1. El contenido de la localidad apuntada por el PC se deposita en el
registro de instrucciones IR, la unidad de control ordena copiar en el registro r el
contenido del registro s, PC PC + 1, y el contador de programa apunta a la
siguiente instrucción a ejecutarse.
Los grupos de instrucciones que utilizan este tipo de direccionamiento de
registro, son los de aritmética y lógica de 8 bits y de 16 bits como son:
1. Rotar
2. Girar
3. Poner en uno
4. Poner en cero y
5. Probar bits
-DIRECCIONAMIENTO EXTENDIDO-
En este tipo de direccionamiento la dirección de la palabra de instrucción
contiene la dirección eficaz de la memoria, este es, las instrucciones que utilizan
direccionamiento extendido proporcionan los 16 bits de la dirección eficaz de la
memoria, el campo de la dirección eficaz contiene un operando, con este modo de
direccionamiento se pueden accesar directamente todas las localidades de memoria
desde 0000h hasta FFFFh, el primer byte contiene los 8 bits menos significativos (LSB)
de la dirección eficaz y el segundo byte contiene los 8 bits más significativos (MSB) de
la dirección eficaz.
Ejemplo: LD A,(1580h)
OP - CODE 3A
LSB 80
50
Instituto Tecnológico Superior de Misantla
MSB 15
11xxx111 RST m
Localidad m
0000 0
0008 1
0010 2
0018 3
0020 4
0028 5
0030 6
0038 7
51
Instituto Tecnológico Superior de Misantla
Las direcciones especificas de la pagina zero se obtienen al multiplicar por 8 el valor binario de
la variable m, por ejemplo si m = 2 la dirección es 10h.
RST m OP-CODE
RST 0 C7
RST 8 CF
RST 10 D7
RST 18 DF
RST 20 E7
RST 28 EF
RST 30 F7
RST 38 FF
-DIRECCIONAMIENTO RELATIVO-
En el direccionamiento relativo a la dirección proporcionada por el registro de
instrucciones se le añade una dirección de referencia, normalmente esta dirección de
referencia es el contador de programa PC, es posible obtener dos modos diferentes de
direccionamiento relativo :
1).- Relativo hacia adelante
2).- Relativo hacia atrás
52
Instituto Tecnológico Superior de Misantla
-DIRECCIONAMIENTO DE BIT-
La CPU Z80 tiene instrucciones que permiten probar, poner en cero y poner en
uno a los bits de un operando:
1. BIT b, r
2. RES b, r
3. SET b, r
Estas instrucciones efectúan operaciones a nivel de bits sobre los contenidos de
las localidades de memoria o los registros de la CPU Z80 seleccionados por medio de
uno de tres tipos de direccionamiento, de registro, de registro indirecto e indexado, con
tres bits del código operacional se especifica cual de los 8 bits del operando es el
involucrado.
Ejemplo: SET b, r
En donde "b" puede ser cualquier numero del 0h al 7h y "r" es cualquier registro
primario, acumulador, registro par HL o registro de índice IX o IY.
CAMPO b BIT =1
53
Instituto Tecnológico Superior de Misantla
-DIRECCIONAMIENTO INDEXADO-
Con este modo se obtiene la dirección efectiva haciendo la suma de:
1. El contenido de un registro especial del Z80 denominado "registro de índice".
2. El registro de índice se suma con el operando que proporciona la instrucción.
La indexación permite tratar bucles durante un programa, si se dispone de varios
registros de índice se pueden añadir varios bucles, normalmente el registro de índice
se incrementa por software después de cada operación.
Las instrucciones que utilizan direccionamientos indexado usan byte que sigue al
código operacional para especificar el desplazamiento que suma a uno de los dos
registros de índice del Z80, para formar la dirección efectiva de la memoria, el
contenido del registro de índice no se altera.
Ejemplo:
54
Instituto Tecnológico Superior de Misantla
-OPERACION PUSH –
Un dato de 16 bits se transfiere al área de la pila de la memoria desde un
registro par o desde el PC durante una operación PUSH, las direcciones del área de
memoria que son accesadas durante la operación PUSH se determinan de la siguiente
manera:
1.Los 8 bits mas significativos del dato se almacenan en la localidad de memoria
con dirección igual al contenido actual del apuntador de pila de la memoria (SP) menos
uno.
2.Los 8 bits menos significativos del dato se almacenan en la localidad de
memoria con dirección igual al contenido actual del apuntador de pila de la memoria
menos dos.
3. El contenido actual del apuntador de pila de la memoria se decrementa
automáticamente por dos.
Por ejemplo sea: PUSH BC
(SP) 13A6h, (B) 6A, (C) 30
antes después
1303 FF 1303 FF SP
1304 FF 1304 30 C
1305 FF 1305 6A B
1306 FF SP 1306 FF
1307 FF 1307 FF
- OPERACION POP-
Un dato de 16 bits se transfiere desde la pila de la memoria a un registro par o al
contador del programa durante una operación POP.
55
Instituto Tecnológico Superior de Misantla
Las direcciones del área de memoria que son accesadas durante la operación
POP se determinan de la siguiente manera:
1.El segundo registro del par o los 8 bits menos significativos del contador de
programa se cargan con el contenido de la localidad direccionada por el apuntador de
la pila.
2.El primer registro del par o los 8 bits mas significativos del contador de
programa se cargan con el contenido de la localidad direccionada por el apuntador de
la pila mas uno.
3.El contenido actual de la pila se incrementa automáticamente por dos.
Ejemplo: POP BC
antes después
1507 FF 1507 FF
1508 33 SP 1508 33
1509 0C 1509 0C
150A FF 150A FF SP
150B FF 150B FF
(B) 6A (B) 0C
(C) 30 (C) 33
56
Instituto Tecnológico Superior de Misantla
Ensamblador
Lenguaje de Bajo Nivel
57
Instituto Tecnológico Superior de Misantla
programa, sería la propia máquina la que pasaría los grupos de caracteres a bits.
Las ventajas de esto son evidentes, ya que para el hombre resulta mas fácil manipular
grupos de caracteres y la traducción se haría de manera automática. Por ejemplo, se
podría escribir:
TRASLADAR 11010110, 00011101
Esto indicaría que el contenido de la posición 11010110 había que pasarlo a la posición
00011101 si se sabe que al grupo alfabético TRASLADAR le corresponde la secuencia
de bits 11110101. La máquina traduciría la anterior instrucción como:
11110101 11010110 00011101
.
Ventajas De Los Lenguajes Ensambladores.
El corazón de la computadora es el microprocesador, éste maneja las
necesidades aritméticas, de lógica y de control de la computadora.
El microprocesador tiene su origen en la década de los sesenta, cuando se diseño el
circuito integrado (IC por sus siglas en ingles) al combinar varios componentes
electrónicos en un solo componente sobre un "chip" de silicio. Los fabricantes
58
Instituto Tecnológico Superior de Misantla
59
Instituto Tecnológico Superior de Misantla
Asignar memoria
Cuando se inicializa un nuevo proceso, el motor de tiempo de ejecución reserva
una región contigua de espacio de direcciones para el proceso. Este espacio de
direcciones reservado se denomina montón administrado. El montón administrado
mantiene un puntero a la dirección a la que se asignará el siguiente objeto del montón.
Inicialmente, este puntero se establece en la dirección base del montón administrado.
Todos los tipos de referencia se asignan en el montón administrado. Cuando una
aplicación crea el primer tipo de referencia, se le asigna memoria en la dirección base
del montón administrado. Cuando la aplicación crea el siguiente objeto, el recolector de
elementos no utilizados le asigna memoria en el espacio de direcciones que sigue
inmediatamente al primer objeto. Siempre que haya espacio de direcciones disponible,
el recolector de elementos no utilizados continúa asignando espacio a los objetos
nuevos de este modo.
La asignación de memoria desde el montón administrado es más rápida que la
asignación de memoria no administrada. Como el tiempo de ejecución asigna memoria
a los objetos agregando un valor a un puntero, este método es casi tan rápido como la
asignación de memoria desde la pila. Además, puesto que los nuevos objetos que se
asignan consecutivamente se almacenan uno junto a otro en el montón administrado, la
aplicación puede tener un acceso muy rápido a los objetos.
Liberar memoria
El motor de optimización del recolector de elementos no utilizados determina
cuál es el mejor momento para realizar una recolección basándose en las asignaciones
realizadas. Cuando el recolector lleva a cabo una recolección, libera la memoria de los
objetos que ya no usa la aplicación. Determina qué objetos ya no se usan examinando
las raíces de la aplicación. Todas las aplicaciones tienen un conjunto de raíces. Cada
60
Instituto Tecnológico Superior de Misantla
raíz hace referencia a un objeto del montón administrado, o bien se establece en null.
Las raíces de una aplicación incluyen punteros de objeto globales y estáticos, variables
locales y parámetros de objetos de referencia en la pila de un subproceso, y registros
de la CPU. El recolector de elementos no utilizados tiene acceso a la lista de raíces
activas que mantienen el compilador Just-In-Time (JIT) y el motor de tiempo de
ejecución. Con esta lista examina las raíces de la aplicación y, durante este proceso,
crea un gráfico que contiene todos los objetos que no se pueden alcanzar desde las
raíces.
Los objetos que no están en el gráfico no se pueden alcanzar desde las raíces
de la aplicación. El recolector considera los objetos inalcanzables elementos no
utilizados y libera la memoria que tienen asignada. Durante una recolección, el
recolector de elementos no utilizados examina el montón administrado y busca los
bloques de espacio de direcciones que ocupan los objetos que no se pueden alcanzar.
Cuando encuentra cada uno de los objetos inalcanzables, usa una función de copia de
memoria para compactar los objetos alcanzables en la memoria y libera los bloques de
espacios de direcciones asignados a los objetos no alcanzables. Una vez que se ha
compactado la memoria de los objetos alcanzables, el recolector de elementos no
utilizados hace las correcciones de puntero necesarias para que las raíces de la
aplicación señalen a los objetos en sus nuevas ubicaciones. También sitúa el puntero
del montón administrado después del último objeto alcanzable. Tenga en cuenta que la
memoria sólo se compacta si, durante una recolección, se encuentra un número
significativo de objetos inalcanzables. Si todos los objetos del montón administrado
sobreviven a una recolección, no hay necesidad de comprimir la memoria.
Para mejorar el rendimiento, el tiempo de ejecución asigna memoria a los
objetos grandes en un montón aparte. El recolector de elementos no utilizados libera la
memoria para los objetos grandes automáticamente. Sin embargo, para no mover
objetos grandes en la memoria, dicha memoria no se compacta.
Generaciones y rendimiento
Para optimizar el rendimiento del recolector de elementos no utilizados, el
montón administrado se divide en tres generaciones: 0, 1 y 2. El algoritmo de
recolección de elementos no utilizados del motor de tiempo de ejecución se basa en
varias afirmaciones que la industria de software informático ha comprobado como
ciertas experimentando con esquemas de recolección de elementos no utilizados.
Primero, es más rápido compactar la memoria de una parte del montón administrado
que la de todo el montón. En segundo lugar, los objetos más recientes tienen una
duración más corta y los objetos antiguos tienen una duración más larga. Por último,
los objetos más recientes suelen estar relacionados unos con otros y la aplicación tiene
acceso a ellos más o menos al mismo tiempo.
El recolector de elementos no utilizados del motor de tiempo de ejecución
guarda los nuevos objetos en la generación 0. Los objetos creados en las primeras
etapas de la duración de la aplicación y que sobreviven a las recolecciones se
promueven y se almacenan en las generaciones 1 y 2. El proceso de promoción de
objetos se describe más adelante en este tema. Como es más rápido compactar una
parte del montón administrado que todo el montón, este esquema permite que el
recolector de elementos no utilizados libere la memoria en una generación específica
61
Instituto Tecnológico Superior de Misantla
en lugar de liberarla para todo el montón administrado cada vez que realiza una
recolección.
En realidad, el recolector de elementos no utilizados realiza una recolección
cuando se llena la generación 0. Si una aplicación trata de crear un nuevo objeto
cuando la generación 0 está llena, el recolector de elementos no utilizados descubre
que no queda espacio de direcciones en la generación 0 para asignárselo. El recolector
de elementos no utilizados realiza una recolección, en un intento de liberar espacio de
direcciones para el objeto en la generación 0. Primero examina los objetos de la
generación 0 y no todos los objetos del montón administrado. Éste es un enfoque más
eficaz, ya que los objetos nuevos suelen tener una duración más corta y se espera que
la aplicación no utilice muchos de los objetos de la generación 0 cuando se realice una
recolección. Además, una recolección de tan sólo la generación 0 a menudo recupera
suficiente memoria para que la aplicación pueda continuar creando nuevos objetos.
Una vez que el recolector de elementos no utilizados realiza una recolección de
la generación 0, compacta la memoria de los objetos que se pueden alcanzar como se
ha explicado antes en este tema, en Liberar memoria. A continuación, el recolector de
elementos no utilizados promueve estos objetos y considera que esta parte del montón
administrado está en la generación 1. Puesto que los objetos que sobreviven a las
recolecciones suelen tener una duración más larga, es lógico promoverlos a una
generación superior. En consecuencia, el recolector de elementos no utilizados no tiene
que volver a examinar los objetos de las generaciones 1 y 2 cada vez que realiza una
recolección en la generación 0.
Una vez que el recolector de elementos no utilizados ha realizado la primera
recolección de la generación 0 y ha promovido los objetos que se pueden alcanzar a la
generación 1, considera lo que queda del montón administrado como generación 0.
Continúa asignando memoria a los nuevos objetos en la generación 0 hasta que está
llena y es necesario realizar otra recolección. En este momento, el motor de
optimización del recolector de elementos no utilizados determina si es necesario
examinar los objetos de generaciones más antiguas. Por ejemplo, si una recolección de
la generación 0 no recupera memoria suficiente para que la aplicación pueda terminar
satisfactoriamente su intento de crear un nuevo objeto, el recolector de elementos no
utilizados puede realizar una recolección de la generación 1 y, después, de la
generación 0. Si así no se recupera suficiente memoria, el recolector de elementos no
utilizados puede realizar una recolección de las generaciones 2, 1 y 0. Después de
cada recolección, el recolector de elementos no utilizados compacta los objetos
alcanzables de la generación 0 y los promueve a la generación 1. Los objetos de la
generación 1 que se siguen utilizando después de la recolección se promueven a la
generación 2. Como el recolector de elementos no utilizados sólo admite tres
generaciones, los objetos de la generación 2 que se siguen utilizando después de una
recolección permanecen en la generación 2 hasta que se determina, en una
recolección posterior, que no se pueden alcanzar.
62
Instituto Tecnológico Superior de Misantla
63
Instituto Tecnológico Superior de Misantla
Evaluación
63
Instituto Tecnológico Superior de Misantla
Bibliografía
1. Aho, Sethi, Ullman. Compiladores Principios, técnicas y herramientasEd. Addison Wesley.
2. Lemone Karen A. , Fundamentos de compiladores Cómo traducir al lenguaje de
computadora, Ed. Compañía Editorial Continental.
3. Kenneth C. Louden. Construcción de compiladores Principios y práctica.Ed. Thomson.
4. Martin John, Lenguajes formales y teoría de la computación, ED. Mc Graw Hill
5. Hopcroft John E., Introducción a la Teoría de Autómatas, Lenguajes y Computación, ED.
Addison Wesley
6. Guerra Crespo. Hector. Compiladores. Ed. Tecnologica didáctica.
7. Ronald Mak. Writing compilers and interpreters. Ed. Wiley Computer Publishing.
8. Fischer, LeBlanc. Crafting a compiler with C. Ed. Cummings Publishing Company, Inc.
9. Salas Parrilla, Jesús. Sistemas Operativos y Compiladores. McGraw Hill.
10. Beck. Software de Sistemas, Introducción a la programación de Sistemas. Addison-
Wesley Iberoamericana.
11. Teufel, Schmidt, Teufel. Compiladores Conceptos Fundamentales. Addison-Wesley
Iberoamericana.
12. C. Louden, Kenneth. Lenguajes de programación Principios y práctica. Thomson.
13. Levine Gutiérrez, Guillermo. Computación y programación moderna Perspectiva integral
de la informática. Pearson Educación.
14. Abel, Peter. Lenguaje ensamblador y programación para PC IBM y compatibles.
Pearson Educación.
15. Mak, Ronald. Writing compilers and interpreters. Wiley Computer Publishing.
16. Pittman, Thomas, Peters, James. The art of compiler design Theory and practice.
Prentice Hall.
17. Temblay & Sorenson. Compilers Writing. Mc Graw Hill.
18. R. Levine, John; Mason, Tony, Brown, Doug. Lex y Yacc. O'Reilly & Associates.
19. The Lex & Yacc Page, 3-mar-04, 12:45, http://dinosaur.compilertools.net
20. A compact guide to lex & Yacc, Thomas Niemann, 3-Mar-04, 12:50,
http://epaperpress.com/lexandyacc
21. Lex & Yacc HOWTO, Bert Hubert (PowerDNS.COM.BV), 3-Mar-04, 12:55,
http://ds9a.nl/lex_yacc
22. Flex, 3-mar-04, 13:02, http://www.gnu.org/software/flex/flex.html
23. Compiler construction using flex and Bison, Anthony Aaby, 3-mar-04, 13:05,
http://cs.wwc.edu/aabyan/464/BooK/
24. Flex, version 2.5 A fast scanner generator, Edition 2.5, March 1995, Vern Paxson, 3-mar-
04, 13:10, http://www.cs.princelon.edu/appel/modern/c/software/flex/flex_toc.html
25. Bison. The Yacc-compatible Parser Generator, November 1995, Bison Version 1.5,
Charles Donnelly and Richard Stallman, 3-mar-04, 13:10,
http://www.cs.princelon.edu/appel/modern/c/software/bison/bison_toc .html, 13/dic/2009
26. Bison. http://3d2f.com/programs/30-170-microprocessor-emulator-and- assembler-
download.shtml, 13/dic/2009
27. 2/Ago/2005 ,Microprocessor Emulator and Assembler 3.10-k,
http://software.intel.com/en-us/articles/all/1/, 24/feb/2010
28. Intel, 31/dic/2009, Intel® Software Development EmulatorBottom of Form,
http://software.intel.com/en‐us/articles/int el‐software‐development‐emulator/, 24/feb/2010
64