La citazione sopra riportata è corretta: il compilatore in genere non conosce l'indirizzo delle variabili locali in fase di compilazione. Detto questo, il compilatore probabilmente conosce l'offset dalla base del frame dello stack a cui verrà localizzata una variabile locale, ma a seconda della profondità dello stack di chiamata, che potrebbe tradursi in un indirizzo diverso in fase di esecuzione. A titolo di esempio, si consideri questo codice ricorsivo (che, tra l'altro, non è affatto buono codice!):
int Factorial(int num) {
int result;
if (num == 0)
result = 1;
else
result = num * Factorial(num - 1);
return result;
}
A seconda del parametro num
, questo codice potrebbe finire per fare diverse chiamate ricorsive, quindi non c'è saranno più copie di result
in memoria, ognuna con un valore diverso. Di conseguenza, il compilatore non può sapere dove andranno tutti. Tuttavia, ogni istanza di result
sarà probabilmente sfalsata della stessa quantità dalla base del frame dello stack contenente ciascuna invocazione Factorial
, anche se in teoria il compilatore potrebbe eseguire altre operazioni come l'ottimizzazione di questo codice in modo che sia presente una sola copia di result
.
In genere, i compilatori allocano le variabili locali mantenendo un modello del frame dello stack e del tracciamento in cui si trova la successiva posizione libera nello stack frame. In questo modo, le variabili locali possono essere allocate rispetto all'inizio del frame dello stack e, quando viene chiamata la funzione, è possibile utilizzare quell'indirizzo relativo, in combinazione con l'indirizzo dello stack, per cercare la posizione di tale variabile nello specifico frame dello stack .
Le variabili globali, d'altra parte, possono avere i loro indirizzi noti in fase di compilazione. Differiscono dai locali principalmente in quanto vi è sempre una copia di una variabile globale in un programma. Le variabili locali possono esistere 0 o più volte a seconda di come va l'esecuzione. Come risultato del fatto che esiste una copia univoca del globale, il compilatore può inserire un indirizzo in hardcode per esso.
Per quanto riguarda ulteriori letture, se si desidera un trattamento abbastanza approfondita di come un compilatore può lay out variabili, si consiglia di prendere una copia di Compilers: Principles, Techniques, and Tools, Second Edition da Aho, Lam, Sethi, e Ullman . Sebbene gran parte di questo libro riguardi altre tecniche di costruzione di compilatori, un'ampia sezione del libro è dedicata all'implementazione della generazione di codice e alle ottimizzazioni che possono essere utilizzate per migliorare il codice generato.
Spero che questo aiuti!
In un certo senso, nessun indirizzo è noto fino a quando il sistema operativo carica il programma e imposta i mapping della memoria virtuale nella MMU. Il compilatore conosce il layout, cioè l'ordine in cui appaiono le cose, ma tutto è misurato da un indirizzo di base. Per la durata dell'archiviazione statica, viene misurata dall'indirizzo di caricamento del file binario. Per la durata della memorizzazione automatica, viene misurata dal puntatore dello stack. E l'archiviazione dinamica ha un indirizzo determinato in fase di esecuzione, ma i membri vengono archiviati in un offset dall'indirizzo dinamico. –
@ Ben: questo è un problema completamente separato. –