Progettazione del compilatore - Ambiente run-time

Un programma come codice sorgente è semplicemente una raccolta di testo (codice, istruzioni, ecc.) E per renderlo vivo, richiede che vengano eseguite delle azioni sulla macchina di destinazione. Un programma necessita di risorse di memoria per eseguire le istruzioni. Un programma contiene nomi per procedure, identificatori ecc. Che richiedono la mappatura con l'effettiva posizione di memoria in fase di esecuzione.

Per runtime si intende un programma in esecuzione. L'ambiente di runtime è uno stato della macchina di destinazione, che può includere librerie software, variabili di ambiente, ecc. Per fornire servizi ai processi in esecuzione nel sistema.

Il sistema di supporto del runtime è un pacchetto, generato principalmente con il programma eseguibile stesso e facilita la comunicazione del processo tra il processo e l'ambiente di runtime. Si occupa dell'allocazione e della disallocazione della memoria durante l'esecuzione del programma.

Alberi di attivazione

Un programma è una sequenza di istruzioni combinate in una serie di procedure. Le istruzioni in una procedura vengono eseguite in sequenza. Una procedura ha un delimitatore di inizio e di fine e tutto ciò che contiene è chiamato corpo della procedura. L'identificativo della procedura e la sequenza di istruzioni finite al suo interno costituiscono il corpo della procedura.

L'esecuzione di una procedura è chiamata sua attivazione. Un record di attivazione contiene tutte le informazioni necessarie richieste per chiamare una procedura. Un record di attivazione può contenere le seguenti unità (a seconda della lingua di origine utilizzata).

Temporanei Memorizza i valori temporanei e intermedi di un'espressione.
Dati locali Memorizza i dati locali della procedura chiamata.
Stato macchina Memorizza lo stato della macchina come i registri, il contatore dei programmi ecc. Prima che la procedura venga chiamata.
Collegamento di controllo Memorizza l'indirizzo del record di attivazione della procedura del chiamante.
Collegamento di accesso Memorizza le informazioni sui dati che non rientrano nell'ambito locale.
Parametri effettivi Memorizza i parametri effettivi, ovvero i parametri utilizzati per inviare l'input alla procedura chiamata.
Valore di ritorno Memorizza i valori restituiti.

Ogni volta che viene eseguita una procedura, il suo record di attivazione viene memorizzato nello stack, noto anche come stack di controllo. Quando una procedura chiama un'altra procedura, l'esecuzione del chiamante viene sospesa fino al termine dell'esecuzione della procedura chiamata. A questo punto, il record di attivazione della procedura chiamata viene archiviato nello stack.

Assumiamo che il controllo del programma scorra in modo sequenziale e quando una procedura viene chiamata, il suo controllo viene trasferito alla procedura chiamata. Quando viene eseguita una procedura chiamata, restituisce il controllo al chiamante. Questo tipo di flusso di controllo rende più facile rappresentare una serie di attivazioni sotto forma di un albero, noto comeactivation tree.

Per comprendere questo concetto, prendiamo un pezzo di codice come esempio:

. . .
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;
   }
. . .

Di seguito è riportato l'albero di attivazione del codice fornito.

Ora comprendiamo che le procedure vengono eseguite in modo approfondito, quindi l'allocazione dello stack è la forma di archiviazione più adatta per le attivazioni delle procedure.

Allocazione dello spazio di archiviazione

L'ambiente di runtime gestisce i requisiti di memoria di runtime per le seguenti entità:

  • Code: È nota come la parte di testo di un programma che non cambia in fase di esecuzione. I suoi requisiti di memoria sono noti al momento della compilazione.

  • Procedures: La loro parte di testo è statica ma vengono richiamati in modo casuale. Ecco perché lo stack storage viene utilizzato per gestire le chiamate e le attivazioni di procedura.

  • Variables: Le variabili sono note solo in fase di esecuzione, a meno che non siano globali o costanti. Lo schema di allocazione della memoria dell'heap viene utilizzato per gestire l'allocazione e la disallocazione della memoria per le variabili in runtime.

Allocazione statica

In questo schema di allocazione, i dati di compilazione sono associati a una posizione fissa nella memoria e non cambiano quando il programma viene eseguito. Poiché i requisiti di memoria e le posizioni di archiviazione sono noti in anticipo, il pacchetto di supporto runtime per l'allocazione e la disallocazione della memoria non è richiesto.

Allocazione dello stack

Le chiamate di procedura e le loro attivazioni sono gestite mediante allocazione di memoria dello stack. Funziona nel metodo last-in-first-out (LIFO) e questa strategia di allocazione è molto utile per le chiamate di procedure ricorsive.

Allocazione heap

Le variabili locali di una procedura vengono allocate e de-allocate solo in fase di esecuzione. L'allocazione dell'heap viene utilizzata per allocare dinamicamente la memoria alle variabili e rivendicarla quando le variabili non sono più necessarie.

Ad eccezione dell'area di memoria allocata staticamente, sia la memoria dello stack che quella dell'heap possono aumentare e ridursi in modo dinamico e inaspettato. Pertanto, non possono essere forniti con una quantità fissa di memoria nel sistema.

Come mostrato nell'immagine sopra, alla parte di testo del codice viene assegnata una quantità fissa di memoria. La memoria di stack e heap sono disposte agli estremi della memoria totale allocata al programma. Entrambi si restringono e crescono l'uno contro l'altro.

Passaggio dei parametri

Il mezzo di comunicazione tra le procedure è noto come passaggio di parametri. I valori delle variabili da una procedura chiamante vengono trasferiti alla procedura chiamata da qualche meccanismo. Prima di andare avanti, passa prima attraverso alcune terminologie di base relative ai valori in un programma.

valore r

Il valore di un'espressione è chiamato il suo valore r. Il valore contenuto in una singola variabile diventa anche un valore r se appare sul lato destro dell'operatore di assegnazione. I valori r possono sempre essere assegnati a qualche altra variabile.

valore l

La posizione della memoria (indirizzo) in cui è archiviata un'espressione è nota come valore l di tale espressione. Appare sempre a sinistra di un operatore di assegnazione.

Per esempio:

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

Da questo esempio, comprendiamo che valori costanti come 1, 7, 12 e variabili come giorno, settimana, mese e anno hanno tutti valori r. Solo le variabili hanno valori l poiché rappresentano anche la posizione di memoria loro assegnata.

Per esempio:

7 = x + y;

è un errore di valore l, poiché la costante 7 non rappresenta alcuna posizione di memoria.

Parametri formali

Le variabili che accettano le informazioni passate dalla procedura del chiamante sono chiamate parametri formali. Queste variabili sono dichiarate nella definizione della funzione chiamata.

Parametri effettivi

Le variabili i cui valori o indirizzi vengono passati alla procedura chiamata sono chiamate parametri effettivi. Queste variabili sono specificate nella chiamata di funzione come argomenti.

Example:

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

I parametri formali contengono le informazioni del parametro effettivo, a seconda della tecnica di passaggio dei parametri utilizzata. Può essere un valore o un indirizzo.

Passa per valore

Nel meccanismo di passaggio per valore, la procedura chiamante passa il valore r dei parametri effettivi e il compilatore lo inserisce nel record di attivazione della procedura chiamata. I parametri formali contengono quindi i valori passati dalla procedura chiamante. Se i valori contenuti nei parametri formali vengono modificati, non dovrebbe avere alcun impatto sui parametri effettivi.

Passa per riferimento

Nel meccanismo di passaggio per riferimento, il valore l del parametro effettivo viene copiato nel record di attivazione della procedura chiamata. In questo modo, la procedura chiamata ha ora l'indirizzo (posizione di memoria) del parametro effettivo e il parametro formale si riferisce alla stessa posizione di memoria. Pertanto, se il valore puntato dal parametro formale viene modificato, l'impatto dovrebbe essere visto sul parametro effettivo in quanto dovrebbero anche puntare allo stesso valore.

Passa per copia-ripristino

Questo meccanismo di passaggio dei parametri funziona in modo simile al "passaggio per riferimento" tranne per il fatto che le modifiche ai parametri effettivi vengono apportate al termine della procedura chiamata. Alla chiamata della funzione, i valori dei parametri effettivi vengono copiati nel record di attivazione della procedura chiamata. I parametri formali se manipolati non hanno alcun effetto in tempo reale sui parametri effettivi (poiché vengono passati valori l), ma quando la procedura chiamata termina, i valori l dei parametri formali vengono copiati nei valori l dei parametri effettivi.

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 
}

Quando questa funzione termina, il valore l del parametro formale x viene copiato nel parametro effettivo y. Anche se il valore di y viene cambiato prima che la procedura termini, il valore l di x viene copiato nel valore l di y facendolo comportare come una chiamata per riferimento.

Passa per nome

Linguaggi come Algol forniscono un nuovo tipo di meccanismo di passaggio dei parametri che funziona come il preprocessore in linguaggio C. Nel meccanismo di passaggio per nome, il nome della procedura chiamata viene sostituito dal suo corpo effettivo. Pass-by-name sostituisce testualmente le espressioni degli argomenti in una chiamata di procedura per i parametri corrispondenti nel corpo della procedura in modo che ora possa lavorare sui parametri effettivi, in modo molto simile al pass-by-reference.