Diseño del compilador: entorno de tiempo de ejecución

Un programa como código fuente es simplemente una colección de texto (código, declaraciones, etc.) y para hacerlo vivo, requiere que se realicen acciones en la máquina de destino. Un programa necesita recursos de memoria para ejecutar instrucciones. Un programa contiene nombres para procedimientos, identificadores, etc., que requieren mapeo con la ubicación real de la memoria en tiempo de ejecución.

Por tiempo de ejecución, nos referimos a un programa en ejecución. El entorno de ejecución es un estado de la máquina de destino, que puede incluir bibliotecas de software, variables de entorno, etc., para proporcionar servicios a los procesos que se ejecutan en el sistema.

El sistema de soporte en tiempo de ejecución es un paquete, generado principalmente con el programa ejecutable en sí mismo y facilita la comunicación del proceso entre el proceso y el entorno de tiempo de ejecución. Se encarga de la asignación y desasignación de memoria mientras se ejecuta el programa.

Árboles de activación

Un programa es una secuencia de instrucciones combinadas en varios procedimientos. Las instrucciones de un procedimiento se ejecutan secuencialmente. Un procedimiento tiene un delimitador de inicio y final y todo lo que contiene se denomina cuerpo del procedimiento. El identificador del procedimiento y la secuencia de instrucciones finitas que contiene forman el cuerpo del procedimiento.

La ejecución de un procedimiento se denomina activación. Un registro de activación contiene toda la información necesaria para llamar a un procedimiento. Un registro de activación puede contener las siguientes unidades (según el idioma de origen utilizado).

Temporarios Almacena valores temporales e intermedios de una expresión.
Datos locales Almacena datos locales del procedimiento llamado.
Estado de la máquina Almacena el estado de la máquina, como registros, contador de programa, etc., antes de llamar al procedimiento.
Enlace de control Almacena la dirección del registro de activación del procedimiento de llamada.
Enlace de acceso Almacena la información de los datos que están fuera del ámbito local.
Parámetros reales Almacena parámetros reales, es decir, parámetros que se utilizan para enviar entradas al procedimiento llamado.
Valor devuelto Almacena valores devueltos.

Siempre que se ejecuta un procedimiento, su registro de activación se almacena en la pila, también conocida como pila de control. Cuando un procedimiento llama a otro procedimiento, la ejecución del llamador se suspende hasta que el procedimiento llamado finaliza la ejecución. En este momento, el registro de activación del procedimiento llamado se almacena en la pila.

Suponemos que el control del programa fluye de manera secuencial y cuando se llama a un procedimiento, su control se transfiere al procedimiento llamado. Cuando se ejecuta un procedimiento llamado, devuelve el control al llamador. Este tipo de flujo de control facilita la representación de una serie de activaciones en forma de árbol, conocida comoactivation tree.

Para entender este concepto, tomamos un fragmento de código como ejemplo:

. . .
printf(“Enter Your Name: “);
scanf(“%s”, username);
show_data(username);
printf(“Press any key to continue…”);
. . .
int show_data(char *user)
   {
   printf(“Your name is %s”, username);
   return 0;
   }
. . .

A continuación se muestra el árbol de activación del código proporcionado.

Ahora entendemos que los procedimientos se ejecutan en profundidad, por lo que la asignación de pila es la forma más adecuada de almacenamiento para las activaciones de procedimientos.

Asignación de almacenamiento

El entorno de ejecución gestiona los requisitos de memoria en tiempo de ejecución para las siguientes entidades:

  • Code: Se conoce como la parte de texto de un programa que no cambia en tiempo de ejecución. Sus requisitos de memoria se conocen en el momento de la compilación.

  • Procedures: Su parte de texto es estática pero se llaman de forma aleatoria. Por eso, el almacenamiento en pila se utiliza para administrar las llamadas y activaciones de procedimientos.

  • Variables: Las variables solo se conocen en tiempo de ejecución, a menos que sean globales o constantes. El esquema de asignación de memoria dinámica se utiliza para administrar la asignación y desasignación de memoria para las variables en tiempo de ejecución.

Asignación estática

En este esquema de asignación, los datos de compilación están vinculados a una ubicación fija en la memoria y no cambian cuando se ejecuta el programa. Como los requisitos de memoria y las ubicaciones de almacenamiento se conocen de antemano, no se requiere un paquete de soporte de tiempo de ejecución para la asignación y desasignación de memoria.

Asignación de pila

Las llamadas a procedimientos y sus activaciones se gestionan mediante la asignación de memoria de pila. Funciona en el método de último en entrar, primero en salir (LIFO) y esta estrategia de asignación es muy útil para llamadas de procedimiento recursivas.

Asignación de montón

Las variables locales de un procedimiento se asignan y desasignan solo en tiempo de ejecución. La asignación de montón se utiliza para asignar memoria de forma dinámica a las variables y reclamarla cuando las variables ya no sean necesarias.

Excepto el área de memoria asignada estáticamente, tanto la memoria de pila como la de pila pueden crecer y reducirse de forma dinámica e inesperada. Por lo tanto, no se les puede proporcionar una cantidad fija de memoria en el sistema.

Como se muestra en la imagen de arriba, a la parte de texto del código se le asigna una cantidad fija de memoria. La memoria de pila y montón se dispone en los extremos de la memoria total asignada al programa. Ambos se encogen y crecen uno contra el otro.

Paso de parámetros

El medio de comunicación entre procedimientos se conoce como paso de parámetros. Los valores de las variables de un procedimiento de llamada se transfieren al procedimiento llamado mediante algún mecanismo. Antes de seguir adelante, primero repase algunas terminologías básicas relacionadas con los valores de un programa.

valor r

El valor de una expresión se llama valor r. El valor contenido en una sola variable también se convierte en un valor r si aparece en el lado derecho del operador de asignación. Los valores r siempre se pueden asignar a alguna otra variable.

valor l

La ubicación de la memoria (dirección) donde se almacena una expresión se conoce como el valor l de esa expresión. Siempre aparece en el lado izquierdo de un operador de asignación.

Por ejemplo:

day = 1;
week = day * 7;
month = 1;
year = month * 12;

A partir de este ejemplo, entendemos que los valores constantes como 1, 7, 12 y variables como día, semana, mes y año, todos tienen valores r. Solo las variables tienen valores l, ya que también representan la ubicación de memoria que se les asigna.

Por ejemplo:

7 = x + y;

es un error de valor l, ya que la constante 7 no representa ninguna ubicación de memoria.

Parámetros formales

Las variables que toman la información transmitida por el procedimiento de llamada se denominan parámetros formales. Estas variables se declaran en la definición de la función llamada.

Parámetros reales

Las variables cuyos valores o direcciones se pasan al procedimiento llamado se denominan parámetros reales. Estas variables se especifican en la llamada a la función como argumentos.

Example:

fun_one()
{
   int actual_parameter = 10;
   call fun_two(int actual_parameter);
}
   fun_two(int formal_parameter)
{
   print formal_parameter;
}

Los parámetros formales contienen la información del parámetro real, dependiendo de la técnica de paso de parámetros utilizada. Puede ser un valor o una dirección.

Pasar por valor

En el mecanismo de paso por valor, el procedimiento de llamada pasa el valor r de los parámetros reales y el compilador lo coloca en el registro de activación del procedimiento llamado. Los parámetros formales contienen los valores pasados ​​por el procedimiento de llamada. Si se cambian los valores mantenidos por los parámetros formales, no debería tener ningún impacto en los parámetros reales.

Pasar por referencia

En el mecanismo de paso por referencia, el valor l del parámetro real se copia en el registro de activación del procedimiento llamado. De esta manera, el procedimiento llamado ahora tiene la dirección (ubicación de memoria) del parámetro real y el parámetro formal se refiere a la misma ubicación de memoria. Por lo tanto, si se cambia el valor señalado por el parámetro formal, el impacto debe verse en el parámetro real, ya que también deben apuntar al mismo valor.

Pasar por Copiar-restaurar

Este mecanismo de paso de parámetros funciona de manera similar al 'paso por referencia', excepto que los cambios a los parámetros reales se realizan cuando finaliza el procedimiento llamado. Al llamar a la función, los valores de los parámetros reales se copian en el registro de activación del procedimiento llamado. Los parámetros formales, si se manipulan, no tienen un efecto en tiempo real sobre los parámetros reales (a medida que se pasan los valores l), pero cuando finaliza el procedimiento llamado, los valores l de los parámetros formales se copian en los valores l de los parámetros reales.

Example:

int y; 
calling_procedure() 
{
   y = 10;     
   copy_restore(y); //l-value of y is passed
   printf y; //prints 99 
}
copy_restore(int x) 
{     
   x = 99; // y still has value 10 (unaffected)
   y = 0; // y is now 0 
}

Cuando finaliza esta función, el valor l del parámetro formal x se copia al parámetro real y. Incluso si el valor de y se cambia antes de que finalice el procedimiento, el valor l de x se copia al valor l de y, lo que hace que se comporte como una llamada por referencia.

Pasar por nombre

Los lenguajes como Algol proporcionan un nuevo tipo de mecanismo de paso de parámetros que funciona como un preprocesador en el lenguaje C. En el mecanismo de paso por nombre, el nombre del procedimiento que se llama se reemplaza por su cuerpo real. Pass-by-name sustituye textualmente las expresiones de argumento en una llamada a procedimiento por los parámetros correspondientes en el cuerpo del procedimiento para que ahora pueda trabajar con parámetros reales, de forma muy similar a la pass-by-reference.