Examen II - 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 2009
Carnet:
Nombre:
Examen II
(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. Considere la siguiente declaración de un arreglo, en la cual se utiliza sintaxis del estilo de la familia de los
Algol:
a: array [inf..sup] of T
donde inf y sup son constantes de tipo entero, y T es un tipo cualquiera.
Suponga que inf y sup no son constantes estáticas sino constantes que sólo se conocerán dinámicamente
al momento de su nacimiento, esto es, elaboration-time constants. Suponga también que el arreglo a es una
variable local de alguna subrutina, con tiempo de vida local convencional, y que su tamaño será fijo pero no
conocido estáticamente sino determinado al momento de su nacimiento, ya que inf y sup son elaboration-time
constants.
Se desea que Ud. señale la fórmula de cálculo que debe ser utilizada para determinar la dirección de a[i],
suponiendo que i se encuentra en el rango adecuado, y teniendo que:
la variable a tiene asociada la dirección de memoria α, dirección en la cual se tiene almacenado un dope
vector, o descriptor de arreglo, en el que se tiene, empezando en α, los límites inf y sup, en ese orden, y
luego la referencia al contenido del arreglo;
i es una variable entera almacenada en la dirección de memoria β, y
TamT es la cantidad de bytes utilizada por cada elemento del tipo base T.
Utilizaremos ∗X para referirnos al contenido de cualquier dirección de memoria X.
Suponiendo que cada entero ocupa 4 bytes de memoria, la fórmula adecuada es:
a) α + (∗β − ∗α) × TamT.
b) ∗α + (∗β − ∗(α + 4)) × TamT.
c) ∗(α + 8) + (∗β − ∗α) × TamT.
d ) Ninguna de las anteriores.
e) No sabe / No contesta.
0.1. El sistema de tipos de Pascal es considerado inseguro. Esto se debe a que la verificación del buen uso de variantes es demasiado costosa bajo las reglas de Pascal, por lo cual las implementaciones del lenguaje generalmente
omiten tal verificación.
Por ejemplo, usualmente se permite la siguiente violación del sistema de tipos:
var r: record
case b: boolean of
false: (x: real);
true: (y: integer)
end
...
r.b := false;
r.x := 7.2;
r.b := true;
write(r.y)
...
Considere las siguientes características de los lenguajes Algol 68 y Ada, a efectos de analizar por qué en ellos,
a diferencia de Pascal, el sistema de tipos sí es seguro en relación con el manejo de variantes/uniones:
I. En Algol 68 las asignaciones al campo discriminante, esto es, el tag field, (b en nuestro ejemplo) son
implícitamente manejadas por el lenguaje en lugar de explícitamente manejadas por el programador.
II. En Algol 68 la sintaxis no permite acceder a la información de cada una de las variantes sin preguntar
previamente por el valor del campo discriminante implícito.
III. En Ada el campo discriminante puede ser declarado constante/inmutable para algunos registros.
IV. En Ada el campo discriminante no puede ser cambiado sin asignar simultáneamente valores a todos los
otros campos asociados a la variante correspondiente.
¿Cuáles de estas características garantizan la seguridad de las variantes/uniones?
a) Sólo I en Algol 68, y sólo III en Ada.
b) La combinación de I y II en Algol 68, y sólo III en Ada.
c) La combinación de I y II en Algol 68, y sólo IV en Ada.
d ) La combinación de I y II en Algol 68, y la combinación de III y IV en Ada.
e) No sabe / No contesta.
0.2. Considere dos variables de tipo apuntador en un lenguaje tipo Pascal:
p,q: ˆT
donde T es un tipo cualquiera.
Suponga que se está implementando verificación de referencias colgadas (dangling references) mediante lápidas
(tombstones). Por ello, cada apuntador de tipo ˆT está implementado como un apuntador hacia una potencial
lápida, desde la cual se apuntaría potencialmente hacia un objeto de tipo T.
Se desea que Ud. señale qué acciones deben ser ejecutadas a bajo nivel para la instrucción p := q, considerando
que:
p y q están almacenados respectivamente en las direcciones de memoria α y β,
null es una dirección inválida correspondiente a apuntadores nulos,
∗X se refiere al contenido de la dirección de memoria X, y
X ← Y significar almacenar el valor Y en la dirección X.
a) Si ∗β = null, error de referencia colgada;
en caso contrario, α ← ∗β.
b) Si ∗β 6= null y ∗∗β = null, error de referencia colgada;
en caso contrario, α ← ∗β.
c) Si ∗β 6= null y ∗∗β = null, error de referencia colgada;
en caso contrario, α ← ∗∗β.
d ) Si ∗β 6= null y ∗∗β 6= null, error de referencia colgada;
en caso contrario, α ← ∗β.
e) No sabe / No contesta.
0.3. Continúe considerando toda la información de la pregunta 0.2.
Se desea ahora que señale las acciones que deben ser ejecutadas a bajo nivel para la instrucción pˆ := qˆ,
entendiendo que “ˆ” es el operador de indirección y asumiendo que ya se verificó que ninguna de las dos
referencias es nula ni está colgada.
Note además que, en el estilo de Pascal, la asignación corresponde a modelo de valor, y asuma además que
esta asignación es permitida aunque el tipo base T sea un tipo compuesto. Suponga además que cada palabra
de memoria ocupa 4 bytes.
a) α ← ∗β.
b) ∗α ← ∗∗β.
c) ∗∗α + k ← ∗(∗β + k)
para k igual a 0, 4, 8, etcétera, según el tamaño de T.
d ) ∗∗α + k ← ∗(∗∗β + k)
para k igual a 0, 4, 8, etcétera, según el tamaño de T.
e) No sabe / No contesta.
0.4. Continúe considerando toda la información de la pregunta 0.2.
Se desea ahora implementar la acción de alto nivel free(p), correspondiente a liberar el objeto de tipo
T referenciado (a alto nivel) por p. Considere además que el lenguaje no intenta implementar recolección
automática de basura, ni para los objetos de tipo T (a alto nivel) referenciados, pues ése es el objetivo de la
instrucción free, ni para las lápidas.
Con tales condiciones, ¿cuál de las siguientes acciones sería definitivamente incorrecta?
a) Dar error de referencia nula si ∗α = null.
b) Dar error de referencia colgada si ∗α 6= null y ∗∗α = null.
c) Liberar el objeto que empieza en la dirección ∗α si ∗α 6= null.
d ) Liberar el objeto que empieza en la dirección ∗∗α si ∗α 6= null y ∗∗α 6= null.
e) No sabe / No contesta.
0.5. Considere un lenguaje con alcance estático en el que se permita además anidamiento de subrutinas. Al momento
de llamar a una subrutina, ¿quién debe encargarse, entre la subrutina llamadora (caller ) y la subrutina llamada
(callee), de la creación del enlace de la cadena estática, y por qué?
a) La subrutina llamadora, pues es ella quien puede determinar su nivel de anidamiento estático en relación
con la subrutina llamada.
b) La subrutina llamada, pues es ella quien puede determinar su nivel de anidamiento estático en relación
con la subrutina llamadora.
c) La subrutina llamadora, pues, pudiendo cualquiera de las dos determinar tal enlace estático, se ahorra
tiempo en la subrutina llamadora.
d ) La subrutina llamada, pues, pudiendo cualquiera de las dos determinar tal enlace estático, en la subrutina
llamada tal código se tendría una sola vez.
e) No sabe / No contesta.
0.6. Se tiene el siguiente programa escrito en pseudocódigo:
procedure P (x,y,z: int)
x := x + y + z
y := x + y + z
z := x + y + z
endp
/* programa principal */
begin
var a: int := 1
P (a,a,a)
write (a)
end.
Considere para el procedimiento P las siguientes combinaciones de modos de pasaje de sus parámetros: primero,
que x y y sean pasados por valor y z por valor/resultado; segundo, que los tres sean pasados por referencia.
¿Cuál sería la salida del programa en estos dos casos, respectivamente?
a) 7 y 27.
b) 9 y 3.
c) 9 y 27.
d ) Ninguna de las anteriores.
e) No sabe / No contesta.
0.7. En lenguaje C, la declaración de un parámetro formal de tipo arreglo “T a[]” puede omitir el tamaño del
arreglo, pues éste será determinado por el parámetro real. Sin embargo, el tipo base T es imprescindible para
poder determinar en compilación como manejar la aritmética de apuntadores a+k y de indexación a[k].
De acuerdo con esto, en el caso de un arreglo bi-dimensional de memoria contigua almacenado por filas (esto es,
cuyo memory layout usa row-major order continuo y no row-pointer ), ¿qué se puede omitir en la declaración
de un parámetro formal?
a) Sólo el tamaño de la primera dimensión, más no de la segunda: “T a[][N]”.
b) Sólo el tamaño de la segunda dimensión, más no de la primera: “T a[N][]”.
c) Los tamaños de ambas dimensiones, al igual que en el caso uni-dimensional: “T a[][]”.
d ) Ni el tamaño de la primera dimensión, ni el de la segunda: “T a[N][M]”.
e) No sabe / No contesta.
0.8. Continuando con la pregunta 0.7, en el caso de un arreglo multi-dimensional de memoria contigua con más de
dos dimensiones, ¿qué se puede omitir en la declaración del parámetro formal?
a) Los tamaños de todas las dimensiones, excepto el de una de ellas.
b) El tamaño de una sola dimensión, indicando todas las restantes.
c) Los tamaños de todas las dimensiones.
d ) Ninguno de los tamaños, pues se deben indicar todos.
e) No sabe / No contesta.
0.9. El primer mecanismo de implementación de excepciones considerado por Scott en el texto es el de mantener
una pila de manejadores de excepciones durante la ejecución.
Con esta alternativa, ¿cuáles de las siguientes características de la implementación son postuladas por Scott?
I. Se debe empilar un manejador cada vez que se entra a un bloque protegido try.
II. Se debe empilar un manejador cada vez que se entra a un subrutina no protegida.
III. Todo throw debe empilar un manejador.
IV. Todo throw debe desempilar un manejador.
V. Sólo los throw que ocurren en bloques protegidos try deben desempilar un manejador.
a) Sólo I, II y IV.
b) Sólo I, II y V.
c) Sólo I y III.
d ) Sólo I y V.
e) No sabe / No contesta.
0.10. En los encabezados de módulo de Modula-2, los cuales señalan la información pública exportada por el módulo,
se permite la llamada “exportación opaca” de tipos:
TYPE T;
mediante la cual se exporta el nombre de un tipo sin exportar su estructura.
Tales encabezados de módulo proporcionan la única información que es usada por los compiladores a la hora
de procesar otros módulos que importan elementos externos.
La definición de Modula-2 exige que un tipo T exportado en el encabezado de manera opaca sea implementado
en el cuerpo del módulo como un apuntador hacia cualquier otro tipo base.
¿Por qué Modula-2 impone esta restricción?
a) Porque en un lenguaje que maneja las variables mediante modelo de referencia, no es posible hacer
exportación no-opaca de tipos.
b) Porque en un lenguaje que maneja las variables mediante modelo de referencia, todo tipo debe ser
declarado como una referencia.
c) Porque en un lenguaje que maneja las variables mediante modelo de valor, tanto los tipos opacos como
los no-opacos deben ser declarados como referencias.
d ) Porque en un lenguaje que maneja las variables mediante modelo de valor, esto permite al compilador
calcular cuánto espacio ocupa cada variable de un tipo importado opaco.
e) No sabe / No contesta.
0.11. Las clases de C++ pueden tener declaraciones abreviadas a efectos de manejar compilación separada, considerando a tales declaraciones abreviadas como encabezados de clase similares a los encabezados de módulo de
Modula-2.
Por ejemplo, un archivo list_node.h podría contener la declaración abreviada
class list_node {
list_node* prev;
list_node* next;
public:
int val;
list_node();
list_node* predecessor();
list_node* successor();
}
en la que se declaran tres atributos prev, next y val, un constructor sin argumentos, y dos métodos
predecessor y successor para la clase list_node, manteniendo los dos primeros atributos como no-públicos.
Luego, en otro archivo list_node.cc habría que tener una declaración completa para la clase en la cual se
implementen los cuerpos de todos sus constructores y métodos, lo cual sería considerado entonces el cuerpo
propiamente dicho de la clase.
El objetivo de esta separación es permitir que las clases clientes de la clase list_node hagan inclusión en su
código fuente de la declaración abreviada contenida en list_node.h a efectos de compilación.
La zona pública de esta declaración abreviada permite al compilador:
a) Determinar la cantidad de espacio que ocuparán las variables de tipo list_node declaradas en las clases
clientes, ya que C++ utiliza modelo de valor para las variables.
b) Determinar la cantidad de espacio que ocuparán las variables de tipo list_node declaradas en las clases
clientes, ya que C++ utiliza modelo de referencia para las variables.
c) Determinar cuáles atributos y métodos públicos son utilizables directamente para variables de tipo
list_node declaradas en las clases clientes, y compilar entonces apropiadamente.
d ) Determinar cuáles atributos y métodos privados son utilizables directamente para variables de tipo
list_node declaradas en las clases clientes, y compilar entonces apropiadamente.
e) No sabe / No contesta.
0.12. Continuando con la pregunta 0.11, el hecho de que en esta declaración abreviada se incluyan atributos privados
permite al compilador:
a) Determinar la cantidad de espacio que ocuparán las variables de tipo list_node declaradas en las clases
clientes, ya que C++ utiliza modelo de valor para las variables.
b) Determinar la cantidad de espacio que ocuparán las variables de tipo list_node declaradas en las clases
clientes, ya que C++ utiliza modelo de referencia para las variables.
c) Determinar cuáles atributos y métodos públicos son utilizables directamente para variables de tipo
list_node declaradas en las clases clientes, y compilar entonces apropiadamente.
d ) Determinar cuáles atributos y métodos privados son utilizables directamente para variables de tipo
list_node declaradas en las clases clientes, y compilar entonces apropiadamente.
e) No sabe / No contesta.
0.13. Considere los lenguajes orientados por objetos C++ y Java, y suponga que se tiene una declaración de variable
A x;
siendo A alguna clase concreta.
En C++, al nacer la variable x se invoca automáticamente al constructor sin argumentos de A sobre x, pero
en Java tal constructor sin argumentos de A no es invocado al nacer la variable x sino cada que vez que se
ejecute la expresión new A().
Esta diferencia se debe a que:
a) En Java se permite tener objetos no inicializados por el constructor correspondiente mientras que en
C++ esto no se permite.
b) En Java no es posible determinar en qué momento nacerá la variable x mientras que en C++ sí.
c) En C++ las clases no sólo tienen constructores sino también destructores, mientras que en Java no hay
destructores.
d ) En uno de los dos lenguajes las variables se manejan bajo modelo de valor y en el otro bajo modelo de
referencia.
e) No sabe / No contesta.
0.14. A diferencia de Java, en los lenguajes orientados por objetos C++ y Eiffel la herencia permite que una subclase
oculte métodos públicos obtenidos de su superclase. En el caso de C++, esto se logra indicando en la herencia
si ésta se quiere hacer pública, protegida o privada, lo cual indica el nivel de visibilidad al que se reducen los
atributos y métodos heredados con visibilidad mayor. La sintaxis de herencia en C++ es como en el siguiente
ejemplo:
class
class
class
class
A
B
C
D
{
:
:
:
... }
public A { ... }
protected A { ... }
private A { ... }
en el que B hereda con nivel de visibilidad público de A, C hereda con nivel de visibilidad protegido de A, y D
hereda con nivel de visibilidad privado de A.
Para obtener un buen polimorfismo de subtipos con binding dinámico de métodos, ¿qué tipos de referencias
tiene sentido que sean asignadas a una variable de tipo A*?
a) Ni referencias a objetos de tipo B, ni C, ni D.
b) Sólo referencias a objetos de tipo B, pero no C ni D.
c) Sólo referencias a objetos de tipo B o C, pero no D.
d ) Referencias cualesquiera a objetos de tipo B, C o D.
e) No sabe / No contesta.
◦
Pregunta 1 - 10 puntos
Se desea que Ud. construya en Prolog un pequeño analizador de equivalencia estructural de tipos de un cierto
lenguaje L. Para ello Ud. debe dar reglas de Prolog que implementen un predicado equiv(T0,T1), donde T0 y T1
son descriptores de tipos del lenguaje L, esto es, expresiones de Prolog que representan a un tipo de L.
En el lenguaje L se tienen los siguientes tipos:
tipos básicos int, float y bool para enteros, reales y booleanos
con descriptores de tipos denotados por los átomos int, float y bool de Prolog;
arreglos multi-dimensionales array [ inf0 .. sup0 , inf1 .. sup1 , . . . ] of T con inf0, sup0, inf1, sup1, . . . enteros
conocidos al momento de hacer el análisis de equivalencia estructural tales que inf0 6 sup0, inf1 6 sup1,
etcétera, y con T un tipo cualquiera
con descriptores de tipos denotados por la estructura array(Ls,DT) de Prolog, donde Ls es la lista (no vacía)
de límites [ limits(Inf0,Sup0), limits(Inf1,Sup1), ... ] de las dimensiones del arreglo y DT es el
descriptor de tipo del tipo base T del arreglo;
estructuras/registros struct { n0 : T0 ; n1 : T1 ; . . . } con n0, n1, . . . nombres para los campos de la estructura
y T0, T1, . . . tipos cualesquiera asociados a los campos
con descriptores de tipos denotados por la estructura struct(Cs) de Prolog, donde Cs es la lista (no vacía)
de campos [ field(N0,DT0), field(N1,DT1), ... ] con cada Ni el átomo correspondiente a cada nombre
y cada DTi el descriptor de tipo del tipo T i;
uniones/variantes union { n0 : T0 ; n1 : T1 ; . . . } con n0, n1, . . . nombres para las posibles variantes de la
unión y T0, T1, . . . tipos cualesquiera asociados a las variantes
con descriptores de tipos denotados por la estructura union(Vs) de Prolog, donde Vs es la lista (no vacía)
de variantes [ variant(N0,DT0), variant(N1,DT1), ... ] con cada Ni el átomo correspondiente a cada
nombre y cada DTi el descriptor de tipo del tipo T i;
tipos definidos por el usuario, creados mediante definiciones de la forma type n = T en las que no se permite
recursión (ni directa ni indirecta), con n el nombre dado al nuevo tipo y T el tipo asociado al nuevo nombre
representados mediante hechos de Prolog de la forma defn(N,DT) con N el átomo correspondiente al nombre
y DT el descriptor de tipo del tipo T .
Ahora bien, en cuanto a las reglas de equivalencia estructural del lenguaje L, tenemos lo siguiente:
cada tipo básico es equivalente consigo mismo;
cada tipo arreglo es equivalente con cualquier otro tipo arreglo que (i) tenga la misma cantidad de dimensiones
y el mismo tamaño para cada dimensión, aunque los límites inferiores y superiores específicos sean distintos,
y (ii) tenga tipo base estructuralmente equivalente (por ejemplo, array [ 0 .. 7 , 0 .. 5 ] of int es equivalente a
array [ 1 .. 8 , 10 .. 15 ] of int pero no a array [ 0 .. 5 , 0 .. 7 ] of int );
cada tipo estructura/registro es equivalente con cualquier otro tipo estructura/registro que (i) tenga la
misma cantidad de campos con los mismos nombres, aunque los campos estén declarados en orden distinto, y (ii) los campos con los mismos nombres tengan tipos estructuralmente equivalentes (por ejemplo,
struct { a : int ; b : bool } es equivalente a struct { b : bool ; a : int } pero no a struct { c : int ; d : bool } );
cada tipo unión/variante es equivalente con cualquier otro tipo unión/variante que (i) tenga la misma cantidad
de posibles variantes con los mismos nombres, aunque estén declaradas en orden distinto, y (ii) las variantes
con los mismos nombres tengan tipos estructuralmente equivalentes (por ejemplo, union { a : int ; b : bool } es
equivalente a union { b : bool ; a : int } pero no a union { c : int ; d : bool } );
cuando se tiene un tipo definido por el usuario mediante type n = T , se tiene entonces que n es estructuralmente equivalente a T y a cualquier otro tipo equivalente a T .
Construya entonces utilizando Prolog el analizador de equivalencia estructural de tipos de L implementando el
predicado deseado equiv(T0,T1). Ud. puede suponer que los dos argumentos de equiv siempre se reciben totalmente
instanciados, y puede suponer también que la base de hechos de Prolog cuenta ya con la información de los tipos
definidos por el usuario.
Ud. puede utilizar cualquier predicado tradicional predefinido de Prolog si explica su significado.
Descargar