Examen parcial I - LDC - Universidad Simón Bolívar

Anuncio
Universidad Simón Bolívar
Departamento de Computación y Tecnología de la Información
CI3641 – Lenguajes de Programación I
Septiembre-Diciembre 2008
Carnet:
Nombre:
Examen parcial I
(40 puntos)
Antes de empezar, revise bien el examen.
Pregunta 0
Pregunta 1
Total
30 puntos
10 puntos
40 puntos
Pregunta 0 - 30 puntos
Esta pregunta consta de quince (15) subpreguntas de selección, numeradas de 0.0 a 0.14. Cada una de las
subpreguntas viene acompañada de cuatro posibles respuestas (a,b,c,d ), entre las cuales sólo una es correcta.
Ud. deberá marcar la opción que considere correcta o, si desea no contestar la subpregunta, marcar la última
opción (e) que indica que Ud. prefiere omitir la respuesta.
Cada una de las quince (15) subpreguntas tiene un valor de dos (2) puntos. Tres subpreguntas malas eliminan
una buena. Las subpreguntas omitidas (marcadas en la opción (e)) no suman ni restan puntos.
◦
0.0. Suponga que estamos trabajando con uno de los dialectos de Fortran pre-90, en los que no se permite que
las subrutinas sean recursivas, ni directa ni indirectamente. Sabemos entonces que la reserva de espacio
para las variables locales de una subrutina puede ser realizada tanto estáticamente como en pila.
Suponga que contamos con el grafo de llamadas potenciales de un programa escrito en este dialecto de Fortran pre-90. Este grafo tiene como nodos a las subrutinas del programa, incluyendo el programa principal,
y como aristas dirigidas (P, Q) aquéllas en las que el texto de la subrutina origen P contiene una llamada
a la subrutina destino Q. Suponga además que los nodos de este grafo están decorados con la cantidad de
espacio requerida por cada subrutina para sus variables locales.
En el caso de que la reserva de espacio local para las subrutinas sea realizada estáticamente, indique qué
característica del grafo de llamadas potenciales puede ser utilizada para determinar la cantidad mínima de
espacio total requerida por el programa para sus variables:
a) La sumatoria del peso de todos los nodos del grafo.
b) El peso mínimo de todos los caminos que empiezan en el programa principal, entendiendo por peso
de un camino la sumatoria de los pesos de sus nodos.
c) El peso máximo de todos los caminos que empiezan en el programa principal, entendiendo por peso
de un camino la sumatoria de los pesos de sus nodos.
d ) El peso del nodo correspondiente al programa principal.
e) No sabe / No contesta.
0.1. Continuando con la pregunta 0.0 sobre Fortran pre-90, en el caso de que la reserva de espacio local para las
subrutinas sea realizada en pila, indique qué característica del grafo de llamadas potenciales corresponde
a la cantidad mínima de espacio total que podría utilizar el programa para sus variables:
a) La sumatoria del peso de todos los nodos del grafo.
b) El peso mínimo de todos los caminos que empiezan en el programa principal, entendiendo por peso
de un camino la sumatoria de los pesos de sus nodos.
c) El peso máximo de todos los caminos que empiezan en el programa principal, entendiendo por peso
de un camino la sumatoria de los pesos de sus nodos.
d ) El peso del nodo correspondiente al programa principal.
e) No sabe / No contesta.
0.2. Continuando con la pregunta 0.0 sobre Fortran pre-90, en el caso de que la reserva de espacio local para las
subrutinas sea realizada en pila, indique qué característica del grafo de llamadas potenciales corresponde
a la cantidad máxima de espacio total que podría utilizar el programa para sus variables:
a) La sumatoria del peso de todos los nodos del grafo.
b) El peso mínimo de todos los caminos que empiezan en el programa principal, entendiendo por peso
de un camino la sumatoria de los pesos de sus nodos.
c) El peso máximo de todos los caminos que empiezan en el programa principal, entendiendo por peso
de un camino la sumatoria de los pesos de sus nodos.
d ) El peso del nodo correspondiente al programa principal.
e) No sabe / No contesta.
0.3. Continuando con la pregunta 0.0 sobre Fortran pre-90, la ventaja de realizar la reserva de espacio local
para las subrutinas estáticamente en lugar de en pila es:
a) Mejor eficiencia en cuanto a tiempo de ejecución de los programas escritos en el lenguaje, aunque
podría utilizarse más espacio para las variables.
b) Mejor eficiencia en cuanto a espacio utilizado durante la ejecución de los programas escritos en el
lenguaje.
c) Mejor eficiencia tanto de tiempo de ejecución como de espacio utilizado para las variables de los
programas escritos en el lenguaje.
d ) Mejor eficiencia de compilación de los programas escritos en el lenguaje.
e) No sabe / No contesta.
0.4. Considere el siguiente programa escrito en pseudocódigo:
var x: integer;
/* variable global */
procedure set_x (n: integer)
x := n
endp
procedure print_x
print (x)
endp
procedure foo (S, P: procedure; n: integer)
var x: integer := 7;
if (n = 3)
set_x (n)
else
S (n)
endif;
if (n = 3)
P
else
print_x
endif
endp
begin
/* programa principal */
set_x (1); foo (set_x, print_x, 3); print_x;
set_x (1); foo (set_x, print_x, 5); print_x
end
Suponga que el pseudolenguaje utilizado maneja alcance dinámico.
Indique entonces cuál es la salida de este programa en el caso de que la asociación (binding) de ambientes
no-locales de subprogramas pasados como parámetro sea superficial (shallow ):
a) 3 3 5 5.
b) 7 3 1 1.
c) 3 1 5 1.
d ) 1 1 7 5.
e) No sabe / No contesta.
0.5. Continuando con la pregunta 0.4, continúe suponiendo que el pseudolenguaje utilizado maneja alcance
dinámico, e indique ahora cuál sería la salida del programa en el caso de que la asociación (binding) de
ambientes no-locales de subprogramas pasados como parámetro sea profunda (deep):
a) 3 3 5 5.
b) 7 3 1 1.
c) 3 1 5 1.
d ) 1 1 7 5.
e) No sabe / No contesta.
0.6. La definición del lenguaje Java establece que el orden de evaluación de los operandos de un operador binario
siempre debe iniciarse con el operando izquierdo, lo cual incluye evaluar todos sus efectos secundarios o de
borde (side effects), y luego continuar con el operando derecho. Esto incluye el caso particular del operador
de asignación “=” como operador binario.
(Nota: La afirmación general del párrafo anterior es un poco simplista, pues no considera ciertos detalles
de algunos casos particulares de Java. A los efectos de esta pregunta, esta simplificación no nos afecta.)
Por otra parte, aunque la definición de Java también establece que la mayoría de sus operadores binarios
asocian a la izquierda, en el caso del operador binario de asignación se establece que éste asocia a la derecha.
En relación con esto, considere el siguiente programa en Java:
class C
{
public static void main(String[] args)
{
int a[] = new int[5];
int i;
for (int k = 0; k < 5; k++)
a[k] = 10 * k + 7;
i = 1;
a[i++] = a[++i] = i++;
}
}
Indique cuál es el estado final de la variable i:
a) 2.
b) 3.
c) 4.
d ) 5.
e) No sabe / No contesta.
0.7. Continuando con la pregunta anterior 0.6, indique cuál es el estado final del arreglo a:
a) 7 17 27 37 47.
b) 7 3 3 37 47.
c) 7 4 27 4 47.
d ) 7 3 27 3 47.
e) No sabe / No contesta.
0.8. Continuando con la pregunta anterior 0.6, algunas personas podrían señalar que luce como una aparente
contradicción el que el operador de asignación asocie a la derecha mientras el orden de evaluación de sus
operandos empieza por la izquierda. ¿Qué se puede decir de tal afirmación?
a) No hay contradicción alguna.
b) La asociación hacia la derecha es una excepción de la regla de evaluación empezando por la izquierda.
c) La asignación debería asociar a la izquierda.
d ) No tiene sentido que el orden de evaluación de los operandos de la asignación empiece por la izquierda;
debería empezar por la derecha.
e) No sabe / No contesta.
0.9. La ventaja de contar con una instrucción de selección condicional del estilo switch/case es que éstas se
pueden implementar mediante tablas de salto en lugar de la correspondiente secuencia de if anidados.
Sobre estas tablas de salto, podemos decir que:
a) Siempre se puede y conviene que se implementen como tablas lineales/directas de salto.
b) Conviene que se implementen como tablas lineales/directas de salto cuando el conjunto de etiquetas
de la instrucción es denso y no contiene grandes rangos.
c) Conviene que se implementen como tablas de hash cuando el conjunto de etiquetas de la instrucción
contiene grandes rangos.
d ) Conviene que se implementen mediante búsqueda binaria únicamente cuando el conjunto de etiquetas
de la instrucción es muy pequeño.
e) No sabe / No contesta.
0.10. Considere los iteradores del lenguaje Clu.
A continuación presentamos un iterador X construido en pseudo-Clu, esto es, pseudocódigo similar al del
lenguaje Clu en el que, para evitar las complicaciones de las estructuras de datos provistas por Clu,
utilizaremos la sencilla versión de tipos recursivos provistos por el lenguaje Haskell:
data Arbol = hoja Int | nodo (Arbol,Arbol)
X = iter (a: Arbol) yields (Arbol)
if (a is nodo(izq,der)) {
for b in X(izq)
yield b
endfor;
yield a;
for b in X(der)
yield b
endfor
}
else
yield a
endif
enditer
En el cuerpo de X, se usa el predicado a is nodo(izq,der) para determinar si el árbol a fue construido
como nodo y, en caso afirmativo, asignar a izq y der las componentes de tal nodo.
Considere ahora la iteración
for z: Arbol in X (nodo (hoja 3, nodo (hoja 5, hoja 7))) do
...
endfor
¿Cuál es el primer árbol procesado por esta iteración?
a) hoja 3.
b) hoja 5.
c) nodo (hoja 5, hoja 7).
d ) Ninguno de los anteriores.
e) No sabe / No contesta.
0.11. Continuando con la pregunta anterior 0.10, ¿cuál es el segundo árbol procesado por la iteración dada?
a) hoja 3.
b) hoja 5.
c) nodo (hoja 5, hoja 7).
d ) Ninguno de los anteriores.
e) No sabe / No contesta.
0.12. Continuando con la pregunta anterior 0.10, ¿cuál es el tercer árbol procesado por la iteración dada?
a) hoja 3.
b) hoja 5.
c) nodo (hoja 5, hoja 7).
d ) Ninguno de los anteriores.
e) No sabe / No contesta.
0.13. Para manejar el problema de posible no-inicialización de variables, Scott en el libro de texto considera, entre
otras posibilidades, el uso de la estrategia de “definite assignment” y el uso de verificaciones dinámicas.
Java y algunos otros lenguajes utilizan la estrategia de “definite assignment”, que podría traducirse como
“inicialización definitiva” o “inicialización estáticamente clara”. Bajo esta estrategia se verifica estáticamente
que las variables sean inicializadas bajo todo posible flujo del control de la ejecución.
Por otra parte, bajo la estrategia de verificaciones dinámicas (dynamic checks) se analiza la posibilidad de
no-inicialización durante la ejecución.
Comparando estas dos alternativas, la ventaja de la inicialización definitiva sobre la verificación dinámica
es:
a) Eficiencia de ejecución de los programas escritos en el lenguaje.
b) Eficiencia de mantenimiento de los programas escritos en el lenguaje.
c) Portabilidad de los programas escritos en el lenguaje.
d ) El hecho de que hay programas sin error de no-inicialización de variables que son rechazados bajo la
estrategia de inicialización definitiva, mientras sí pueden ser procesados exitosamente bajo la estrategia
de verificación dinámica.
e) No sabe / No contesta.
0.14. Continuando con la pregunta anterior 0.13, la ventaja de la verificación dinámica sobre la inicialización
definitiva es:
a) Eficiencia de ejecución de los programas escritos en el lenguaje.
b) Eficiencia de mantenimiento de los programas escritos en el lenguaje.
c) Portabilidad de los programas escritos en el lenguaje.
d ) El hecho de que hay programas sin error de no-inicialización de variables que son rechazados bajo la
estrategia de inicialización definitiva, mientras sí pueden ser procesados exitosamente bajo la estrategia
de verificación dinámica.
e) No sabe / No contesta.
◦
Pregunta 1 - 10 puntos
Se desea que Ud. construya, utilizando el lenguaje Haskell, un pequeño fragmento de un compilador de un
lenguaje L que verifica “definite assignment”, esto es, “inicialización definitiva”. Tal como fue descrito en la
pregunta 0.13, bajo esta estrategia se verifica estáticamente que las variables sean inicializadas bajo todo posible
flujo del control de la ejecución.
Para construir el pequeño fragmento deseado, suponemos que ya el compilador analizó sintácticamente el programa de entrada escrito en L, y tiene reflejada toda la información de éste en una estructura del tipo Instr
siguiente:
data Instr =
|
|
|
asign String Expr
sec [Instr]
cond Expr Instr Instr
iter Expr Instr
data Expr = ...
Así, el tipo Instr corresponde a una estructura de árbol de instrucciones, mientras el otro tipo mencionado
Expr permite reflejar la información de una expresión. En Instr, el constructor asign corresponde a asignación
(cuyo lado izquierdo acepta sólo identificadores de variables simples, razón por la cual se usa el tipo String), el
constructor sec corresponde a secuenciación de una lista de instrucciones, el constructor cond corresponde a un
típico condicional if/else, y por último el constructor iter corresponde a una típica iteración while.
Por claridad, asumiremos que el lenguaje L hace una clara distinción entre instrucciones y expresiones, y no
permite híbridos instrucción/expresión como lo hacen otros lenguajes (de la forma de instrucciones con valor o
expresiones con efectos de borde).
Por otra parte, el tipo Expr abarca toda clase de expresiones, sean éstas booleanas, enteras, o de cualquier otro
tipo.
Con esta infraestructura, el fragmento de compilador de L que Ud. debe construir en Haskell es una función
inicializada :: String -> Instr -> Resultado
que determina si el identificador de variable correspondiente a su primer argumento fue “definitivamente” (“definitely”) inicializado en la instrucción/programa correspondiente a su segundo argumento. El resultado será del
tipo
data Resultado = Bien | Mal | Neutro
donde Bien significa que la variable fue bien inicializada, Mal significa que la variable fue utilizada sin estar
“definitivamente” (“definitely”) inicializada, y Neutro significa que la variable no fue inicializada adecuadamente
pero tampoco se intentó utilizar su valor.
Asuma que: (i) dispone de una función ocurre :: String ->Expr ->Bool que permite determinar si un
identificador de variable es utilizado en una expresión, (ii) ya se realizaron todas las verificaciones de declaración
de identificadores y no hay problemas de tal naturaleza, y (iii) también fueron realizadas ya todas las verificaciones
de tipo sin haber encontrado errores. Por último, si así Ud. lo requiere, puede construir funciones auxiliares que
le permitan construir su función inicializada.
Nota: Sólo en caso de que Ud. haya aprobado en algún trimestre anterior al actual el Laboratorio de Lenguajes
de Programación I, puede Ud. opcionalmente responder a esta pregunta utilizando Java en lugar de Haskell,
según Ud. prefiera.
(Espacio adicional)
Descargar