Está en la página 1de 10

9 CONTROL II - PROCEDIMIENTOS Y AMBIENTES ..................................................................................................................

9.1 DEFINICIÓN Y ACTIVACIÓN DE LOS PROCEDIMIENTOS...................................................................................................................... 2


9.2 SEMÁNTICA DE LOS PROCEDIMIENTOS ......................................................................................................................................... 2
9.3 MECANISMOS DE PASO DE PARÁMETROS ..................................................................................................................................... 4
9.3.1 Paso por valor ............................................................................................................................................................... 4
9.3.2 Paso por referencia ....................................................................................................................................................... 5
9.3.3 Paso por valor-resultado ............................................................................................................................................... 5
9.3.4 Paso por nombre y evaluación retardada ..................................................................................................................... 6
9.3.5 Mecanismo de paso de parámetros Vs. la especificación de los parámetros ............................................................... 6
9.3.6 Verificación de tipo de los parámetros .......................................................................................................................... 7
9.4 AMBIENTES, ACTIVACIÓN Y ASIGNACIÓN DE LOS PROCEDIMIENTOS ................................................................................................... 7
9.4.1 Ambientes totalmente estáticos ................................................................................................................................... 7
9.4.2 Ambientes de tiempo de ejecución basados en pilas .................................................................................................... 8
9.4.3 Procedimientos calculados dinámicamente y ambientes totalmente dinámicos ....................................................... 10
9 Control II - Procedimientos y ambientes
 La distinción entre procedimiento y funciones es, en esencia, lo mismo que la diferencia entre expresiones y declaraciones.
 Las llamadas a procedimientos son enunciados, en tanto que las llamadas a función son expresiones.
 De aquí en adelante no se hará una diferencia significativa entre procedimientos y funciones, puesto que, en la mayoría de
los lenguajes, sus propiedades semánticas son similares, aunque su sintaxis no lo sea.
 Los procedimientos surgieron cuando escaseaba la memoria, como una forma de dividir un programa en pequeñas partes
compiladas.
 Los procedimientos también pueden representar procesos recursivos no fácilmente representados por otras estructuras de
control y pueden representar procesos recursivos no fácilmente representables por otras estructuras de control.

9.1 Definición y activación de los procedimientos


 Un procedimiento es un mecanismo en un lenguaje de programación para abstraer un grupo de acciones o de
computaciones.
 El grupo de acciones se conoce como cuerpo del procedimiento, que está representado en su totalidad por el nombre del
procedimiento.
 Se define un procedimiento al proveer un especificación o interfaz y un cuerpo.
 La especificación le da nombre al procedimiento, una lista de los tipos y nombres de sus parámetros, así como el tipo de
su valor devuelto, si es que este existe:

// C++
void c( int& x, int& y ) // Especificación
{
int t = x; // Cuerpo
x = y; // Cuerpo
y = t; // Cuerpo
}

 En algunos lenguajes y en algunas situaciones, puede separarse una especificación de procedimiento de su cuerpo, en
el caso de que la especificación deba estar disponible por adelantado:

void intSwap( int&, int& )

Nótese como esta especificación no requiere que estén especificados los nombres de los parámetros.

 En C++ esta clase de especificación se conoce como una declaración, mientras que la definición completa se llama
definición.
 Se llama o activa un procedimiento al enunciar su nombre, junto con los argumentos de la llamada, que corresponden a
sus parámetros:

intSwap( a, b );

 Una llamada a un procedimiento transfiere el control al principio del procedimiento llamado (el llamado). Cuando la
ejecución llega al final del cuerpo (o algún enunciado de tipo return), el control es devuelto al llamador.
 Algunos lenguajes de programación pueden hacer la distinción entre procedimiento y funciones.
 Un procedimiento se comunica con el resto del programa a través de sus parámetros y también a través de sus
referencias no locales, esto es, referencias a variables declaradas fuera de su propio cuerpo.

9.2 Semántica de los procedimientos


 Un procedimiento es un bloque cuya declaración está separada de su ejecución.
 El ambiente determina la asignación de la memoria y mantiene el significado de los nombres durante la ejecución.

2 Longinos Recuero Bustos (http://longinox.blogspot.com)


 En un lenguaje estructurado en bloques, cuando se encuentra un bloque durante la ejecución, hace que ocurra la
asignación de variables locales y otros objetos correspondientes a las declaraciones del bloque.
 Esta memoria asignada para los objetos locales del bloque se conoce como registro de activación o marco de pila del
bloque, y se dice que el bloque está activado conforme se ejecuta bajo las limitaciones establecidas por su registro de
activación.

A:
{
int x, y;
... Registro de la activación de A
x = y * 10; x
y
B:
{
Registro de la activación de B
int i = x / 2;
i
}
}

 En el código anterior, el bloque B necesita tener acceso a la variable x declarada en el bloque A.


 Una referencia a x dentro de B es una referencia no local. Esto requiere que B retenga información con relación a la
activación que lo rodea.
 Sin embargo, existe una diferencia en la forma en que se resuelven las referencias no locales, si B fuese un
procedimiento llamado desde A.

int x;

void B( void )
{
int i = x / 2;
}
Entorno global
void A( void ) x
{
int x, y; Registro de la activación de A
... x
x = y * 10; y

B();
Registro de la activación de B
}
i
main()
{
A();
return 0;
}

 Bajo la regla de alcance léxica, la x en B es la x global del programa. Por lo tanto, la activación de B debe retener
información con respecto al ambiente global.
 Esto es debido a que el ambiente global es el ambiente definidor de B, en tanto que al registro de activación de A se le
conoce como ambiente invocador de B.
 Para bloques que no sean procedimientos, al ambiente definidor es igual al ambiente invocador.
 El método de comunicación de un procedimiento con su ambiente invocador es a través de sus parámetros.

3 Longinos Recuero Bustos (http://longinox.blogspot.com)


int max( int x, int y )
{
return ( x > y ) ? x : y;
}

 En la llamada a max, el parámetro x es reemplazado por el argumento a + b, y el parámetro y es reemplazado por el


argumento 10. A fin de enfatizar el hecho de que los parámetros no asumen valores, sino hasta que son reemplazados
por los argumentos, los parámetros a veces se conocen como parámetros formales, en tanto que los argumentos se
llaman parámetros actuales.
 Algunos procedimientos pueden depender solamente de parámetros y características fijas del lenguaje. A estos se dice
que están en una forma cerrada, ya que no contienen dependencias no locales. Como ejemplo la función max
anteriormente citada.
 Por otra parte, una definición polimórfica de la misma función en C++, usando plantillas:

template <typename T>


T max( T x, T y )
{
return ( x > y ) ? x : y;
}

no está en forma cerrada, ya que el operador > puede ser sobrecargado.

 Para escribir esta función en forma cerrada, tendríamos que incluir la operación en la lista de parámetros:

template <typename T>


T max( T x, T y, bool ( *gt )( T, T ) )
{
return gt( x, y ) ? x : y;
}

 Si optamos por no hacer esto, entonces la semántica de esta función solamente puede determinarse con relación al
ambiente que la rodea y el código de esta función junto con una representación de su ambiente de definición se llama
cerradura (también llamada clausura léxica o closures), porque puede ser utilizado para resolver todas las referencias no
locales excepcionales con relación al cuerpo de la función.

9.3 Mecanismos de paso de parámetros


 Algunos lenguajes ofrecen sólo una clase básica de mecanismo de paso de parámetros (C, Java, Lenguajes funcionales), en
tanto que otros pueden ofrecer dos (C++) o más.
 En lenguajes que solamente tienen un mecanismo, a menudo es posible también imitar otros, utilizando el direccionamiento
u otras características del lenguaje.

9.3.1 Paso por valor


 Este es, por mucho, el mecanismo más común para el paso de parámetros.
 En este mecanismo, los argumentos son expresiones que se evalúan en el momento de la llamada y sus valores se
convierten en los valores de los parámetros durante la ejecución del procedimiento. Esto significa que los parámetros de
valor se comportan como valores constantes durante la ejecución del procedimiento.
 Esta forma de paso por valor es usualmente el mecanismo de paso de parámetros de los lenguajes funcionales.

4 Longinos Recuero Bustos (http://longinox.blogspot.com)


 El paso por valor es también el mecanismo por omisión en C++ y en Pascal y es en esencia el único mecanismo de paso
por parámetros en C y en Java. Sin embargo, en estos lenguajes los parámetros se consideran como variables locales
del procedimiento, con valores iniciales dados por los argumentos en la llamada.
 Nótese que el paso por valor no implica que no puedan ocurrir cambios fuera del procedimiento mediante el uso de
parámetros. Si el parámetro tiene un tipo de apuntador o de referencia, entonces el valor es una dirección y puede
utilizarse para cambiar la memoria por fuera del procedimiento.
 Además, en algunos lenguajes ciertos valores son de manera implícita apuntadores o referencias:

void init_p_0( int p[] )


{
p[ 0 ] = 0;

p = ( int* ) malloc( sizeof( int ) ); // ERROR!! no tiene efecto


}

 Sin embargo, las asignaciones directas a los parámetros no cambia el argumento fuera del parámetro.

9.3.2 Paso por referencia


 Con este mecanismo un argumento debe ser en principio una variable con una dirección asignada.
 En vez de pasar el valor de la variable, el paso por referencia pasa la ubicación de la variable de modo que el parámetro
se convierte en un alias para el argumento y cualquier cambio que se le haga a éste lo sufre también el argumento.

C C++ Pascal

void inc( int* x ) void inc( int& x ) procedure inc( var x: integer );
{ { begin
( *x )++; x++; x:= x + 1;
} } end

Después de la llamada a inc el valor del argumento aumenta en 1, de manera que ha ocurrido un efecto colateral.
 Es posible el alias múltiple.
 Un problema adicional que debe ser resuelto en un lenguaje con paso por referencia es la respuesta del lenguaje a los
argumentos de referencia que no son variables.

9.3.3 Paso por valor-resultado


 Este mecanismo llega a un resultado similar al del paso por referencia, excepto que en este caso no se establece un
alias real.
 En el procedimiento, el valor el argumento es copiado y utilizado y después, cuando el procedimiento termina, el valor
final del parámetro se copia de regreso a la ubicación del argumento.
 Este método a veces es conocido como copia al entrar, copia al salir, o como copia-restauración.
 El paso por valor-resultado sólo se distingue del paso por referencia por la presencia del alias.

// El código que sigue está escrito en sintaxis C por comodidad

void p( int x, int y )


{
x++;
y++;
}

5 Longinos Recuero Bustos (http://longinox.blogspot.com)


main()
{
int a = 1;
p( a, a );
...
}

a tiene el valor 3 después de que p es llamado en el caso de que se utilice el paso por referencia, en tanto que a tiene el
valor 2 si se utiliza el paso por valor-resultado.
 Los problemas en este mecanismo, son el orden en el que los resultados se copian de regreso a los argumentos y si las
ubicaciones de los argumentos se calculan solamente a la entrada y se almacenan o son recalculados a la salida.

9.3.4 Paso por nombre y evaluación retardada


 La idea del paso por nombre es que no se evalúa el argumento hasta su uso real (como parámetro) en el procedimiento
llamado. Por lo que el nombre del argumento, o su representación textual en el punto de la llamada, reemplaza el nombre
del parámetro al cual corresponde.

int i;
int a[ 10 ];

void inc( int x )


{
i++;
x++;
}

main()
{
i = 1;
a[ 1 ] = 1;
a[ 2 ] = 2;
p( a[ i ] );
return 0;
}

 Este código tiene el resultado de establecer a a[ 2 ] en 3 y dejar a[ 1 ] sin modificación.


 El paso por nombre resulta problemático cuando se desean efectos colaterales.

9.3.5 Mecanismo de paso de parámetros Vs. la especificación de los parámetros


 Es posible criticar estas descripciones de mecanismos de paso de parámetros con base en que están íntimamente
ligados a la mecánica interna del código utilizado para su implementación.
 Todas las interrogantes con respecto a la interpretación que se ha analizado siguen presentándose en el código que
contiene efectos colaterales.
 Un lenguaje que intenta dar solución a este problema es Ada.
 Ada tiene dos conceptos de comunicación de parámetros que se pueden pasar a un procedimiento de tres formas
diferentes (http://es.wikibooks.org/wiki/Programaci%C3%B3n_en_Ada/Subprogramas):
o in: el parámetro formal es una constante y permite sólo la lectura del valor del parámetro real asociado.
o in out: el parámetro formal es una variable y permite la lectura y modificación del valor del parámetro real
asociado.
o out: el parámetro formal es una variable y permite únicamente la modificación del valor del parámetro real
asociado.
Ninguno de estos modos implica el uso de un paso de parámetro por valor o por referencia. El compilador es libre de
elegir el paso más adecuado de acuerdo al tamaño del parámetro y otras consideraciones.
6 Longinos Recuero Bustos (http://longinox.blogspot.com)
procedure Una_Prueba (A, B: in Integer; C: out Integer) is
begin
C:= A + B;
end Una_Prueba;

 Cuando se llame al procedimiento con la sentencia Una_Prueba(5 + P, 48, Q), se evalúan las expresiones 5 + P y 48
(sólo se permiten expresiones en el modo in), después se asignan a los parámetros formales A y B, que se comportan
como constantes. A continuación, se asigna el valor A + B a la variable formal C. Obsérvese que especificando el modo
out no se puede conocer el valor del parámetro real Q. En este caso, el parámetro formal C es una nueva variable cuyo
valor se asignará al parámetro real Q cuando finalice el procedimiento. Si se hubiera querido obtener el valor de Q,
además de poder modificarlo, se debería haber empleado C: in out Integer.

9.3.6 Verificación de tipo de los parámetros


 En lenguajes con tipificado fuerte, las llamadas a procedimientos deben verificarse de modo que los argumentos
concuerden en su tipo y número con los parámetros del procedimiento.
 En el caso del paso por valor referencia, los parámetros deben tener el mismo tipo, pero en el caso del paso por valor
esto puede relajarse hasta la compatibilidad de asignación y pueden permitirse conversiones, como se hace en C, C++ y
Java.

9.4 Ambientes, activación y asignación de los procedimientos


9.4.1 Ambientes totalmente estáticos
 En un lenguaje como Fortran77, toda la asignación de la memoria puede llevarse a cabo en tiempo de carga y las
localizaciones de todas las variables quedan fijas durante toda la ejecución del programa.
 Las definiciones de funciones y de procedimientos no pueden ser anidadas.
 No se permite recursión, por lo tanto, toda la información asociada con una función o con una subrutina puede asignarse
estáticamente.
 Cada procedimiento o función tiene un registro de activación fijo, que ha previsto espacio para las variables y parámetros
locales, así como posiblemente la dirección de retorno para el regreso correcto de las llamadas.
 Las variables globales se definen mediante declaraciones COMMON y se determinan utilizando apuntadores hacia un área
común.
 La forma general del ambiente en tiempo de ejecución para un programa FORTRAN con subrutinas S1…Sn es:

Área COMMON
Registro de activación
de programa principal
Registro de la
activación de S1
Registro de la
activación de S2

Además, cada registro de activación se subdivide en varias áreas:

Espacio para las variables locales


Espacio para los
parámetros pasados
Dirección de retorno
Espacio temporal para
evaluación de la expresión

7 Longinos Recuero Bustos (http://longinox.blogspot.com)


9.4.2 Ambientes de tiempo de ejecución basados en pilas
 En un lenguaje estructurado en bloques con recursión, las activaciones de los bloques de procediemento no pueden
asignarse estáticamente, y que el procedimiento pudiera volver a ser llamado antes de haber salido de su activación
anterior.
 Esto puede hacerse en una forma basada en pilas, con un nuevo registro de activación creado en la pila cada vez que
entra o se libera a la salida de un bloque.
 Para que la información que debe conservarse en el ambiente, para manejar un ambiente basado en pila, es necesario
asignar espacio en una activación para las variables locales, espacio temporal y un apuntador de retorno.
 Sin embargo se requerirá de información adicional:
o Debe mantenerse un apuntador a la activación presente. Este apuntador, debe conservarse en una ubicación
fija, por lo general un registro y se conoce como apuntador ambiente o ep (enviroment pointer).
o Debe conservase, además, un apuntador al registró de activación del bloque desde el que entró dicha
activación. Este apuntador, se conoce como enlace de control o enlace dinámico.
 Los campos en cada uno de los registros de activación deben contener:

enlace de
control
dirección
de retorno
parámetros
pasados
variables
locales
temporales

 Un ejemplo simple de lo anterior, sería:

void p( void ) registro de


{ activación de main ep
... enlace de
registro de activación de q
} control
enlace de
void q( void ) control
{ memoria
p(); libre
}

main()
{
q(); Dirección de crecimiento de la pila
...
}

 Las variables locales se ubican en el registro de activación presente, al cual ep apunta.


 Ya que las variables locales son asignadas como lo exigen las declaraciones del bloque y estas declaraciones son
estáticas, cada vez que entra el bloque se asignan las mismas clases de variables en el mismo orden. Por tanto, cada
variable puede ser ubicada en la misma posición en el registro de activación con relación al principio de mismo.
 Esta posición se conoce como desplazamiento de la variable local. Puede encontrarse cada variable local utilizando su
desplazamiento fijo a partir de la ubicación marcada por ep.
 En el siguiente ejemplo, cualquier registro de activación de p tendrá este formato:

8 Longinos Recuero Bustos (http://longinox.blogspot.com)


int x;

void p( int y )
{
int i = x;
char c;
...
}

void q( int a )
{
int x;
...
p( 1 );
}

main()
{
q( 2 );
return 0;
}

 En el caso de las referencias no locales, por ejemplo la referencia x a en p. Si el lenguaje no permite anidar
procedimientos (C, Fortran), todas las referencias no locales por fuera de un procedimiento son realmente globales y se
asignan estáticamente.
 Sin embargo, si el lenguaje permite anidar procedimientos (Pascal, Ada, Modula 2), las referencias no locales ahora
pueden ser variables locales en el alcance de procedimiento que lo rodea:

procedure q is
x: integer;

procedure p( y: integer ) is
i: integer := x;
begin
...
end p;

procedure r is
x: float;
begin
p( 1 );
...
end r;

begin
r;
end q;

 En el ejemplo anterior, para encontrar la referencia no local a la x de q desde el interior de p, se podría seguir el enlace
de control hasta el registro de activación de r, pero con ello encontraríamos la x local de r, logrando un alcance dinámico
en vez de un alcance léxico.
 Para lograr el alcance léxico, un procedimiento es que p mantenga un enlace a su ambiente léxico o de definición.
 A este enlace se le conoce como enlace de acceso o enlace estático. Ahora cada registro de activación necesita de un
nuevo campo, e campo del enlace de acceso.

9 Longinos Recuero Bustos (http://longinox.blogspot.com)


 Cuando los bloques están anidados profundamente, para encontrar una referencia no local, pudiera ser necesario seguir
varios enlaces de acceso en vez de uno solo.
 Este procedimiento se llama encadenamiento de accesos y la cantidad de enlaces de accesos que deberían seguirse
corresponde a la diferencia en niveles de anidamiento, o profundidad de anidamiento, entre el ambiente de acceso y el
ambiente definidor de la variable que se está accesando.
 Con este orden en el ambiente, la cerradura de un procedimiento (el código dl procedimiento, junto con un mecanismo para
resolver referencias no locales) se hace significativamente más complejo, ya que cada vez que un procedimiento es
llamado, debe incluirse como parte del registro de activación el ambiente definidor de dicho proceso.
 Entonces un leguaje como Ada o Pascal deben representarse no sólo por apuntador al código para el procedimiento, sino
también por una cerradura que consiste en un par de apuntadores:
o El apuntador del código o de la instrucción (ip).
o El apuntador de enlace de acceso o de ambiente de su ambiente definidor (ep).
 Esta clausura la escribiremos como <ep, ip>.

9.4.3 Procedimientos calculados dinámicamente y ambientes totalmente dinámicos


 Un ambiente basado en pilas tiene sus limitaciones.
 Por ejemplo, cualquier procedimiento que pueda devolver un apuntador a un objeto local, dará como resultado una
referencia pendiente al salir del procedimiento ya que el registro de activación del procedimiento habrá sido desasignado
de la pila.
 Ocurre una situación más severa si el diseñador del lenguaje desea extender la expresividad y la flexibilidad del lenguaje
al permitir que los procedimientos puedan ser creados dinámicamente.
 En un lenguaje de este tipo, los procedimientos se convierten en lo que se conoce como valores de primera clase.
 No obstante, se podría desear la capacidad de hacer cosas parecidas a las comentadas en los puntos anteriores y para
ello el tipo de ambiente debe ser totalmente dinámico, porque elimina los registros de activación sólo cuando ya no es
posible tener acceso a ellos desde el interior del programa en ejecución.
 Un ambiente de este tipo debe llevar a cabo alguna clase de recuperación automática de almacenamiento no accesible.
 Dos métodos estándar para ello son los conteos de referencia y la recolección de basura.
 Esta situación también quiere decir que la estructura de las activaciones se ramifica en vez de parecerse a una pila.
 Este es el modelo que ejecutan Scheme y otros lenguajes funcionales.

10 Longinos Recuero Bustos (http://longinox.blogspot.com)

También podría gustarte